diff --git a/.gitignore b/.gitignore index 9d49ca2b4d..329bbb7e01 100644 --- a/.gitignore +++ b/.gitignore @@ -70,9 +70,6 @@ bin/ *.cache /.nb-gradle/private/ -#PMD files +# PMD files .pmd .ruleset - -#Android Studio -local.properties \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 91c9bf9146..9c5c0f6909 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java jdk: -- oraclejdk7 +- openjdk8 # force upgrade Java8 as per https://github.com/travis-ci/travis-ci/issues/4042 (fixes compilation issue) #addons: @@ -10,9 +10,9 @@ jdk: # prevent travis running gradle assemble; let the build script do it anyway install: true - + +# running in container causes test failures and 2x-3x longer build, use standalone instances sudo: required -# as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ # script for build and release via Travis to Bintray script: gradle/buildViaTravis.sh @@ -20,6 +20,7 @@ script: gradle/buildViaTravis.sh # Code coverage after_success: - bash <(curl -s https://codecov.io/bash) + - bash gradle/push_javadoc.sh # cache between builds cache: diff --git a/CHANGES.md b/CHANGES.md index 4454aec878..3321e0f855 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,876 @@ The changelog of version 1.x can be found at https://github.com/ReactiveX/RxJava/blob/1.x/CHANGES.md +### Version 2.2.20 - October 6, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.20%7C)) +[JavaDocs](http://reactivex.io/RxJava/2.x/javadoc/2.2.20) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + +- Fix `Observable.flatMap` with `maxConcurrency` hangs (#6960) +- Fix `Observable.toFlowable(ERROR)` not cancelling upon `MissingBackpressureException` (#7084) +- Fix `Flowable.concatMap` backpressure with scalars. (#7091) + +### Version 2.2.19 - March 14, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.19%7C)) +[JavaDocs](http://reactivex.io/RxJava/2.x/javadoc/2.2.19) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + + - [Commit 7980c85b](https://github.com/ReactiveX/RxJava/commit/7980c85b18dd46ec2cd2cf49477363f1268d3a98): Fix `switchMap` not canceling properly during `onNext`-`cancel` races. + + +### Version 2.2.18 - February 21, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.18%7C)) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + + - [Pull 6894](https://github.com/ReactiveX/RxJava/pull/6894): Fix `groupBy` not requesting more if a group is cancelled with buffered items. + +### Version 2.2.17 - January 12, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.17%7C)) + +#### Bugfixes + + - [Pull 6827](https://github.com/ReactiveX/RxJava/pull/6827): Fix `Flowable.flatMap` not canceling the inner sources on outer error. + +### Version 2.2.16 - December 15, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.16%7C)) + +#### Bugfixes + + - [Pull 6754](https://github.com/ReactiveX/RxJava/pull/6754): Fix `amb`, `combineLatest` and `zip` `Iterable` overloads throwing `ArrayStoreException` for `ObservableSource`s. + +#### Documentation changes + + - [Pull 6746](https://github.com/ReactiveX/RxJava/pull/6746): Fix self-see references, some comments. + +### Version 2.2.15 - November 24, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.15%7C)) + +#### Bugfixes + + - [Pull 6715](https://github.com/ReactiveX/RxJava/pull/6715): Fix `MulticastProcessor` not requesting more after limit is reached. + - [Pull 6710](https://github.com/ReactiveX/RxJava/pull/6710): Fix concurrent `clear` in `observeOn` while output-fused. + - [Pull 6720](https://github.com/ReactiveX/RxJava/pull/6720): Fix `parallel()` on grouped flowable not replenishing properly. + +#### Documentation changes + + - [Pull 6722](https://github.com/ReactiveX/RxJava/pull/6722): Update javadoc for `observeOn` to mention its eagerness. + +#### Other changes + + - [Pull 6704](https://github.com/ReactiveX/RxJava/pull/6704): Add ProGuard rule to avoid `j.u.c.Flow` warnings due to RS 1.0.3. + +### Version 2.2.14 - November 2, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.14%7C)) + +#### Bugfixes + + - [Pull 6677](https://github.com/ReactiveX/RxJava/pull/6677): Fix concurrent `clear()` calls when fused chains are canceled. + - [Pull 6684](https://github.com/ReactiveX/RxJava/pull/6684): Fix `window(time)` possible interrupts while terminating. + +#### Documentation changes + + - [Pull 6681](https://github.com/ReactiveX/RxJava/pull/6681): Backport marble diagrams for `Single` from 3.x. + +### Version 2.2.13 - October 3, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.13%7C)) + +#### Dependencies + + - [Commit cc690ff2](https://github.com/ReactiveX/RxJava/commit/cc690ff2f757873b11cd075ebc22262f76f28459): Upgrade to **Reactive Streams 1.0.3**. + +#### Bugfixes + + - [Commit cc690ff2](https://github.com/ReactiveX/RxJava/commit/cc690ff2f757873b11cd075ebc22262f76f28459): Avoid using `System.getProperties()`. + - [Pull 6653](https://github.com/ReactiveX/RxJava/pull/6653): Fix `takeLast(time)` last events time window calculation. + - [Pull 6657](https://github.com/ReactiveX/RxJava/pull/6657): Fix size+time bound `window` not creating windows properly. + +### Version 2.2.12 - August 25, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.12%7C)) + +#### Bugfixes + + - [Pull 6618](https://github.com/ReactiveX/RxJava/pull/6618): Fix `switchMap` incorrect sync-fusion & error management. + - [Pull 6627](https://github.com/ReactiveX/RxJava/pull/6627): Fix `blockingIterable` hang when force-disposed. + - [Pull 6629](https://github.com/ReactiveX/RxJava/pull/6629): Fix `refCount` not resetting when cross-canceled. + +### Version 2.2.11 - August 2, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.11%7C)) + +#### Bugfixes + + - [Pull 6560](https://github.com/ReactiveX/RxJava/pull/6560): Fix NPE when debouncing an empty source. + - [Pull 6599](https://github.com/ReactiveX/RxJava/pull/6599): Fix `mergeWith` not canceling other when the main fails. + - [Pull 6601](https://github.com/ReactiveX/RxJava/pull/6601): `ObservableBlockingSubscribe` compares with wrong object. + - [Pull 6602](https://github.com/ReactiveX/RxJava/pull/): Fix truncation bugs in `replay()` and `ReplaySubject`/`Processor`. + +#### Documentation changes + + - [Pull 6565](https://github.com/ReactiveX/RxJava/pull/6565): Fix JavaDocs of `Single.doOnTerminate` refer to `onComplete` notification. + +### Version 2.2.10 - June 21, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.10%7C)) + +#### Bugfixes + + - [Pull 6499](https://github.com/ReactiveX/RxJava/pull/6499): Add missing null check to `BufferExactBoundedObserver`. + - [Pull 6505](https://github.com/ReactiveX/RxJava/pull/6505): Fix `publish().refCount()` hang due to race. + - [Pull 6522](https://github.com/ReactiveX/RxJava/pull/6522): Fix `concatMapDelayError` not continuing on fused inner source crash. + +#### Documentation changes + + - [Pull 6496](https://github.com/ReactiveX/RxJava/pull/6496): Fix outdated links in `Additional-Reading.md`. + - [Pull 6497](https://github.com/ReactiveX/RxJava/pull/6497): Fix links in `Alphabetical-List-of-Observable-Operators.md`. + - [Pull 6504](https://github.com/ReactiveX/RxJava/pull/6504): Fix various Javadocs & imports. + - [Pull 6506](https://github.com/ReactiveX/RxJava/pull/6506): Expand the Javadoc of `Flowable`. + - [Pull 6510](https://github.com/ReactiveX/RxJava/pull/6510): Correct "Reactive-Streams" to "Reactive Streams" in documentation. + +### Version 2.2.9 - May 30, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.9%7C)) + +#### Bugfixes + + - [Pull 6488](https://github.com/ReactiveX/RxJava/pull/6488): Fix `zip` not stopping the subscription upon eager error. + +#### Documentation changes + + - [Pull 6453](https://github.com/ReactiveX/RxJava/pull/6453): Fixed wrong type referenced in `Maybe` and `Single` JavaDocs. + - [Pull 6458](https://github.com/ReactiveX/RxJava/pull/6458): Update the Javadoc of the `retry` operator. + +#### Other + + - [Pull 6452](https://github.com/ReactiveX/RxJava/pull/6452): Remove dependency of `Schedulers` from `ObservableRefCount`. + - [Pull 6461](https://github.com/ReactiveX/RxJava/pull/6461): Change error message in `ObservableFromArray`. + - [Pull 6469](https://github.com/ReactiveX/RxJava/pull/6469): Remove redundant methods from `sample(Observable)`. + - [Pull 6470](https://github.com/ReactiveX/RxJava/pull/6470): Remove unused import from `Flowable.java`. + - [Pull 6485](https://github.com/ReactiveX/RxJava/pull/6485): Remove unused `else` from the `Observable`. + +### Version 2.2.8 - March 26, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.8%7C)) + +#### Bugfixes + + - [Pull 6442](https://github.com/ReactiveX/RxJava/pull/6442): Add missing undeliverable error handling logic for `Completable.fromRunnable` & `fromAction` operators. + +#### Documentation changes + + - [Pull 6432](https://github.com/ReactiveX/RxJava/pull/6432): Improve the docs of `CompositeDisposable`. + - [Pull 6434](https://github.com/ReactiveX/RxJava/pull/6434): Improve subjects and processors package doc. + - [Pull 6436](https://github.com/ReactiveX/RxJava/pull/6436): Improve `Creating-Observables` wiki doc. + + +#### Other + + - [Pull 6433](https://github.com/ReactiveX/RxJava/pull/6433): Make error messages of parameter checks consistent. + +### Version 2.2.7 - February 23, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.7%7C)) + +#### API enhancements + + - [Pull 6386](https://github.com/ReactiveX/RxJava/pull/6386): Add `doOnTerminate` to `Single`/`Maybe` for consistency. + +#### Bugfixes + + - [Pull 6405](https://github.com/ReactiveX/RxJava/pull/6405): Fix concatEager to dispose sources & clean up properly. + - [Pull 6398](https://github.com/ReactiveX/RxJava/pull/6398): Fix `window()` with start/end selector not disposing/cancelling properly. + +#### Documentation changes + + - [Pull 6377](https://github.com/ReactiveX/RxJava/pull/6377): Expand `Observable#debounce` and `Flowable#debounce` javadoc. + - [Pull 6408](https://github.com/ReactiveX/RxJava/pull/6408): Improving Javadoc of `flattenAsFlowable` and `flattenAsObservable` methods. + +### Version 2.2.6 - January 23, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.6%7C)) + +#### API enhancements + + - [Pull 6370](https://github.com/ReactiveX/RxJava/pull/6370): Add interruptible mode via the new `Schedulers.from(Executor, boolean)` overload. + +#### Bugfixes + + - [Pull 6359](https://github.com/ReactiveX/RxJava/pull/6359): Fix the error/race in `Observable.repeatWhen` due to flooding repeat signal. + - [Pull 6362 ](https://github.com/ReactiveX/RxJava/pull/6362 ): Fix `Completable.andThen(Completable)` not running on `observeOn`'s `Scheduler`. + - [Pull 6364](https://github.com/ReactiveX/RxJava/pull/6364): Fix `Flowable.publish` not requesting upon client change. + - [Pull 6371](https://github.com/ReactiveX/RxJava/pull/6371): Fix bounded `replay()` memory leak due to bad node retention. + - [Pull 6375](https://github.com/ReactiveX/RxJava/pull/6375): Don't dispose the winner of `{Single|Maybe|Completable}.amb()`. + - [Pull 6380](https://github.com/ReactiveX/RxJava/pull/6380): Fix `CompositeException.getRootCause()` detecting loops in the cause graph. + +#### Documentation changes + + - [Pull 6365](https://github.com/ReactiveX/RxJava/pull/6365): Indicate source disposal in `timeout(fallback)`. + +#### Other changes + + - [Pull 6353](https://github.com/ReactiveX/RxJava/pull/6353): Use `ignoreElement` to convert `Single` to `Completable` in the `README.md`. + +### Version 2.2.5 - December 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.5%7C)) + +#### Documentation changes + + - [Pull 6344](https://github.com/ReactiveX/RxJava/pull/6344): Use correct return type in JavaDocs documentation in `elementAtOrDefault`. + - [Pull 6346](https://github.com/ReactiveX/RxJava/pull/6346): Fix JavaDoc examples using markdown instead of `@code`. + +#### Wiki changes + + - [Pull 6324](https://github.com/ReactiveX/RxJava/pull/6324): Java 8 version for [Problem-Solving-Examples-in-RxJava](https://github.com/ReactiveX/RxJava/wiki/Problem-Solving-Examples-in-RxJava). + - [Pull 6343](https://github.com/ReactiveX/RxJava/pull/6343): Update [Filtering Observables](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) docs. + - [Pull 6351](https://github.com/ReactiveX/RxJava/pull/6351): Updated java example in [How-To-Use-RxJava.md](https://github.com/ReactiveX/RxJava/wiki/How-To-Use-RxJava) file with java 8 version. + +#### Other changes + + - [Pull 6313](https://github.com/ReactiveX/RxJava/pull/6313): Adding `@NonNull` annotation factory methods. + - [Pull 6335](https://github.com/ReactiveX/RxJava/pull/6335): Replace indexed loop with for-each java5 syntax. + +### Version 2.2.4 - November 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.4%7C)) + +#### API changes + + - [Pull 6278](https://github.com/ReactiveX/RxJava/pull/6278): Add `Maybe`/`Single`/`Completable` `materialize` operator, + - [Pull 6278](https://github.com/ReactiveX/RxJava/pull/6278): Add `Single.dematerialize(selector)` operator. + - [Pull 6281](https://github.com/ReactiveX/RxJava/pull/6281): Add `Flowable`/`Observable` `dematerialize(selector)` operator. + +#### Bugfixes + + - [Pull 6258](https://github.com/ReactiveX/RxJava/pull/6258): Fix cancel/dispose upon upstream switch for some operators. + - [Pull 6269](https://github.com/ReactiveX/RxJava/pull/6269): Call the `doOn{Dispose|Cancel}` handler at most once. + - [Pull 6283](https://github.com/ReactiveX/RxJava/pull/6283): Fix `Observable.flatMap` to sustain concurrency level. + - [Pull 6297](https://github.com/ReactiveX/RxJava/pull/6297): Fix refCount eager disconnect not resetting the connection. + +#### Documentation changes + + - [Pull 6280](https://github.com/ReactiveX/RxJava/pull/6280): Improve the package docs of `io.reactivex.schedulers`. + - [Pull 6301](https://github.com/ReactiveX/RxJava/pull/6301): Add missing `onSubscribe` null-checks to NPE docs on `Flowable`/`Observable` `subscribe`. + - [Pull 6303](https://github.com/ReactiveX/RxJava/pull/6303): Fix incorrect image placement in `Flowable.zip` docs. + - [Pull 6305](https://github.com/ReactiveX/RxJava/pull/6305): Explain the non-concurrency requirement of the `Emitter` interface methods. + - [Pull 6308](https://github.com/ReactiveX/RxJava/pull/6308): Explain the need to consume both the group sequence and each group specifically with `Flowable.groupBy`. + - [Pull 6311](https://github.com/ReactiveX/RxJava/pull/6311): Explain that `distinctUntilChanged` requires non-mutating data to work as expected. + +#### Wiki changes + + - [Pull 6260](https://github.com/ReactiveX/RxJava/pull/6260): Add `generate` examples to `Creating-Observables.md`. + - [Pull 6267](https://github.com/ReactiveX/RxJava/pull/6267): Fix `Creating-Observables.md` docs stlye mistake. + - [Pull 6273](https://github.com/ReactiveX/RxJava/pull/6273): Fix broken markdown of `How-to-Contribute.md`. + - [Pull 6266](https://github.com/ReactiveX/RxJava/pull/6266): Update Error Handling Operators docs. + - [Pull 6291](https://github.com/ReactiveX/RxJava/pull/6291): Update Transforming Observables docs. + +#### Other changes + + - [Pull 6262](https://github.com/ReactiveX/RxJava/pull/6262): Use JUnit's assert format for assert messages for better IDE interoperation. + - [Pull 6263](https://github.com/ReactiveX/RxJava/pull/6263): Inline `SubscriptionHelper.isCancelled()`. + - [Pull 6275](https://github.com/ReactiveX/RxJava/pull/6275): Improve the `Observable`/`Flowable` `cache()` operators. + - [Pull 6287](https://github.com/ReactiveX/RxJava/pull/6287): Expose the Keep-Alive value of the IO `Scheduler` as System property. + - [Pull 6321](https://github.com/ReactiveX/RxJava/pull/6321): Fix `Flowable.toObservable` backpressure annotation. + +### Version 2.2.3 - October 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.3%7C)) + +#### API changes + + - [Pull 6242](https://github.com/ReactiveX/RxJava/pull/6242): Add timed `Completable.delaySubscription()` operator. + +#### Documentation changes + + - [Pull 6220](https://github.com/ReactiveX/RxJava/pull/6220): Remove unnecessary 's' from `ConnectableObservable`. + - [Pull 6241](https://github.com/ReactiveX/RxJava/pull/6241): Remove mention of `io.reactivex.functions.Functions` nonexistent utility class. + +#### Other changes + + - [Pull 6232](https://github.com/ReactiveX/RxJava/pull/6232): Cleanup `Observable.flatMap` drain logic. + - [Pull 6234](https://github.com/ReactiveX/RxJava/pull/6234): Add timeout and unit to `TimeoutException` message in the `timeout` operators. + - [Pull 6236](https://github.com/ReactiveX/RxJava/pull/6236): Adjust `UndeliverableException` and `OnErrorNotImplementedException` message to use the full inner exception. + - [Pull 6244](https://github.com/ReactiveX/RxJava/pull/6244): Add `@Nullable` annotations for blocking methods in `Completable`. + +### Version 2.2.2 - September 6, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.2%7C)) + +#### Bugfixes + + - [Pull 6187](https://github.com/ReactiveX/RxJava/pull/6187): Fix `refCount` termination-reconnect race. + +#### Documentation changes + + - [Pull 6171](https://github.com/ReactiveX/RxJava/pull/6171): Add explanation text to `Undeliverable` & `OnErrorNotImplemented` exceptions. + - [Pull 6174](https://github.com/ReactiveX/RxJava/pull/6174): Auto-clean up RxJavaPlugins JavaDocs HTML. + - [Pull 6175](https://github.com/ReactiveX/RxJava/pull/6175): Explain `null` observer/subscriber return errors from `RxJavaPlugins` in detail. + - [Pull 6180](https://github.com/ReactiveX/RxJava/pull/6180): Update `Additional-Reading.md`. + - [Pull 6180](https://github.com/ReactiveX/RxJava/pull/6180): Fix `Flowable.reduce(BiFunction)` JavaDoc; the operator does not signal `NoSuchElementException`. + - [Pull 6193](https://github.com/ReactiveX/RxJava/pull/6193): Add "error handling" java docs section to `fromCallable` & co. + - [Pull 6199](https://github.com/ReactiveX/RxJava/pull/6199): Fix terminology of cancel/dispose in the JavaDocs. + - [Pull 6200](https://github.com/ReactiveX/RxJava/pull/6200): Fix `toFuture` marbles and descriptions. + +### Version 2.2.1 - August 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.1%7C)) + +#### API changes + + - [Pull 6143](https://github.com/ReactiveX/RxJava/pull/6143): Add `concatArrayEagerDelayError` operator (expose feature). + +#### Bugfixes + + - [Pull 6145](https://github.com/ReactiveX/RxJava/pull/6145): Fix boundary fusion of `concatMap` and `publish` operator. + - [Pull 6158](https://github.com/ReactiveX/RxJava/pull/6158): Make `Flowable.fromCallable` consistent with the other `fromCallable`s. + - [Pull 6165](https://github.com/ReactiveX/RxJava/pull/6165): Handle undeliverable error in `Completable.fromCallable` via `RxJavaPlugins`. + - [Pull 6167](https://github.com/ReactiveX/RxJava/pull/6167): Make `observeOn` not let `worker.dispose()` get called prematurely. + +#### Performance improvements + + - [Pull 6123](https://github.com/ReactiveX/RxJava/pull/6123): Improve `Completable.onErrorResumeNext` internals. + - [Pull 6121](https://github.com/ReactiveX/RxJava/pull/6121): `Flowable.onErrorResumeNext` improvements. + +#### Documentation changes + +##### JavaDocs + + - [Pull 6095](https://github.com/ReactiveX/RxJava/pull/6095): Add marbles for `Single.timer`, `Single.defer` and `Single.toXXX` operators. + - [Pull 6137](https://github.com/ReactiveX/RxJava/pull/6137): Add marbles for `Single.concat` operator. + - [Pull 6141](https://github.com/ReactiveX/RxJava/pull/6141): Add marble diagrams for various `Single` operators. + - [Pull 6152](https://github.com/ReactiveX/RxJava/pull/6152): Clarify `TestObserver.assertValueSet` in docs and via tests. + - [Pull 6155](https://github.com/ReactiveX/RxJava/pull/6155): Fix marble of `Maybe.flatMap` events to `MaybeSource`. + +##### Wiki changes + + - [Pull 6128](https://github.com/ReactiveX/RxJava/pull/6128): Remove `fromEmitter()` in wiki. + - [Pull 6133](https://github.com/ReactiveX/RxJava/pull/6133): Update `_Sidebar.md` with new order of topics. + - [Pull 6135](https://github.com/ReactiveX/RxJava/pull/6135): Initial clean up for Combining Observables docs. + - [Pull 6131](https://github.com/ReactiveX/RxJava/pull/6131): Expand `Creating-Observables.md` wiki. + - [Pull 6134](https://github.com/ReactiveX/RxJava/pull/6134): Update RxJava Android Module documentation. + - [Pull 6140](https://github.com/ReactiveX/RxJava/pull/6140): Update Mathematical and Aggregate Operators docs. + +### Version 2.2.0 - July 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.0%7C)) + +#### Summary + +Version 2.2.0 is the next minor release of the 2.x era and contains the standardization of many experimental API additions from the past year since version 2.1.0. Therefore, the following components are now considered stable and will be supported throughout the rest of the life of RxJava 2.x. + +**Classes, Enums, Annotations** + +- **Annotation**: N/A +- **Subject**: `MulticastProcessor` +- **Classes**: `ParallelFlowable`, `UndeliverableException`, `OnErrorNotImplementedException` +- **Enum**: `ParallelFailureHandling` +- **Interfaces**: `{Completable|Single|Maybe|Observable|Flowable|Parallel}Emitter`, `{Completable|Single|Maybe|Observable|Flowable|Parallel}Converter`, `LambdaConsumerIntrospection`, `ScheduledRunnableIntrospection` + +**Operators** + +- **`Flowable`**: `as`, `concatMap{Single|Maybe|Completable}`, `limit`, `parallel`, `switchMap{Single|Maybe|Completable}`, `throttleLatest` +- **`Observable`**: `as`, `concatMap{Single|Maybe|Completable}`, `switchMap{Single|Maybe|Completable}`, `throttleLatest` +- **`Single`**: `as`, `mergeDelayError`, `onTerminateDetach`, `unsubscribeOn` +- **`Maybe`**: `as`, `mergeDelayError`, `switchIfEmpty` +- **`Completable`**: `as`, `fromMaybe`, `onTerminateDetach`, `takeUntil` +- **`ParallelFlowable`**: `as`, `map|filter|doOnNext(errorHandling)`˙, `sequentialDelayError` +- **`Connectable{Flowable, Observable}`**: `refCount(count + timeout)` +- **`Subject`/`FlowableProcessor`**: `offer`, `cleanupBuffer`, `create(..., delayError)` +- **`Test{Observer, Subscriber}`**: `assertValueAt`, `assertValuesOnly`, `assertValueSetOnly` + +*(For the complete list and details on the promotions, see [PR 6105](https://github.com/ReactiveX/RxJava/pull/6105).)* + +Release 2.2.0 is functionally identical to 2.1.17. Also to clarify, just like with previous minor version increments with RxJava, there won't be any further development or updates on the version 2.1.x (patch) level. + +##### Other promotions + +All Experimental/Beta APIs introduced up to version 2.1.17 are now standard with 2.2. + +#### Project statistics + +- Unique contributors: **75** +- Issues closed: [**283**](https://github.com/ReactiveX/RxJava/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x+) +- Bugs reported: [**20**](https://github.com/ReactiveX/RxJava/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x+label%3Abug) + - by community: [**19**](https://github.com/ReactiveX/RxJava/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x+label%3Abug+-author%3Aakarnokd) (95%) +- Commits: [**320**](https://github.com/ReactiveX/RxJava/compare/v2.1.0...2.x) +- PRs: [**296**](https://github.com/ReactiveX/RxJava/pulls?q=is%3Apr+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x) + - PRs accepted: [**268**](https://github.com/ReactiveX/RxJava/pulls?q=is%3Apr+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x) (90.54%) + - Community PRs: [**96**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+created%3A2017-04-29..2018-07-31+label%3A2.x+-author%3Aakarnokd+) (35.82% of all accepted) +- Bugs fixed: [**39**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Abug) + - by community: [**8**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Abug+-author%3Aakarnokd) (20.51%) +- Documentation enhancements: [**117**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Adocumentation) + - by community: [**40**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Adocumentation+-author%3Aakarnokd) (34.19%) +- Cleanup: [**50**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Acleanup) + - by community: [**21**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3Acleanup+-author%3Aakarnokd) (42%) +- Performance enhancements: [**12**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3A%22performance%22+) + - by community: [**1**](https://github.com/ReactiveX/RxJava/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+merged%3A2017-04-29..2018-07-31+label%3A2.x+label%3A%22performance%22+-author%3Aakarnokd) (8.33%) +- Lines + - added: **70,465** + - removed: **12,373** + +#### Acknowledgements + +The project would like to thank the following contributors for their work on various code and documentation improvements (in the order they appear on the [commit](https://github.com/ReactiveX/RxJava/commits/2.x) page): + +@lcybo, @jnlopar, @UMFsimke, @apodkutin, @sircelsius, +@romanzes, @Kiskae, @RomanWuattier, @satoshun, @hans123456, +@fjoshuajr, @davidmoten, @vanniktech, @antego, @strekha, +@artfullyContrived, @VeskoI, @Desislav-Petrov, @Apsaliya, @sidjain270592, +@Milack27, @mekarthedev, @kjkrum, @zhyuri, @artem-zinnatullin, +@vpriscan, @aaronhe42, @adamsp, @bangarharshit, @zhukic, +@afeozzz, @btilbrook-nextfaze, @eventualbuddha, @shaishavgandhi05, @lukaszguz, +@runningcode, @kimkevin, @JakeWharton, @hzsweers, @ggikko, +@philleonard, @sadegh, @dsrees, @benwicks, @dweebo, +@dimsuz, @levaja, @takuaraki, @PhilGlass, @bmaslakov, +@tylerbwong, @AllanWang, @NickFirmani, @plackemacher, @matgabriel, +@jemaystermind, @ansman, @Ganapathi004, @leonardortlima, @pwittchen, +@youngam, @Sroka, @serj-lotutovici, @nathankooij, @mithunsasidharan, +@devisnik, @mg6maciej, @Rémon S, @hvesalai, @kojilin, +@ragunathjawahar, @brucezz, @paulblessing, @cypressf, @langara + +**(75 contributors)** + +The project would also thank its tireless reviewer @vanniktech for all his efforts on verifying and providing feedback on the many PRs from the project lead himself. :+1: + +### Version 2.1.17 - July 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.17%7C)) + +#### API changes + + - [Pull 6079](https://github.com/ReactiveX/RxJava/pull/6079): Add `Completable.takeUntil(Completable)` operator. + - [Pull 6085](https://github.com/ReactiveX/RxJava/pull/6085): Add `Completable.fromMaybe` operator. + +#### Performance improvements + + - [Pull 6096](https://github.com/ReactiveX/RxJava/pull/6096): Improve `Completable.delay` operator internals. + +#### Documentation changes +- [Pull 6066](https://github.com/ReactiveX/RxJava/pull/6066): Fix links for `Single` class. +- [Pull 6070](https://github.com/ReactiveX/RxJava/pull/6070): Adjust JavaDocs `dl`/`dd` entry stylesheet. +- [Pull 6080](https://github.com/ReactiveX/RxJava/pull/6080): Improve class JavaDoc of `Single`, `Maybe` and `Completable`. +- [Pull 6102](https://github.com/ReactiveX/RxJava/pull/6102): Adjust JavaDoc stylesheet of `dt`/`dd` within the method details. +- [Pull 6103](https://github.com/ReactiveX/RxJava/pull/6103): Fix `Completable` `mergeX` JavaDoc missing `dt` before `dd`. +- [Pull 6104](https://github.com/ReactiveX/RxJava/pull/6104): Fixing javadoc's code example of `Observable#lift`. +- Marble diagrams ([Tracking issue 5789](https://github.com/ReactiveX/RxJava/issues/5789)) + - [Pull 6074](https://github.com/ReactiveX/RxJava/pull/6074): `Single.never` method. + - [Pull 6075](https://github.com/ReactiveX/RxJava/pull/6075): `Single.filter` method. + - [Pull 6078](https://github.com/ReactiveX/RxJava/pull/6078): `Maybe.hide` marble diagram. + - [Pull 6076](https://github.com/ReactiveX/RxJava/pull/6076): `Single.delay` method. + - [Pull 6077](https://github.com/ReactiveX/RxJava/pull/6077): `Single.hide` operator. + - [Pull 6083](https://github.com/ReactiveX/RxJava/pull/6083): Add `Completable` marble diagrams (07/17a). + - [Pull 6081](https://github.com/ReactiveX/RxJava/pull/6081): `Single.repeat` operators. + - [Pull 6085](https://github.com/ReactiveX/RxJava/pull/6085): More `Completable` marbles. + - [Pull 6084](https://github.com/ReactiveX/RxJava/pull/6084): `Single.repeatUntil` operator. + - [Pull 6090](https://github.com/ReactiveX/RxJava/pull/6090): Add missing `Completable` marbles (+17, 07/18a). + - [Pull 6091](https://github.com/ReactiveX/RxJava/pull/6091): `Single.amb` operators. + - [Pull 6097](https://github.com/ReactiveX/RxJava/pull/6097): Add missing `Completable` marbles (+19, 07/19a). + - [Pull 6098](https://github.com/ReactiveX/RxJava/pull/6098): Several more `Completable` marbles (7/19b). + - [Pull 6101](https://github.com/ReactiveX/RxJava/pull/6101): Final set of missing `Completable` marbles (+26). + +### Version 2.1.16 - June 26, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.16%7C)) + +This is a hotfix release for a late-identified issue with `concatMapMaybe` and `concatMapSingle`. + +#### Bugfixes + +- [Pull 6060](https://github.com/ReactiveX/RxJava/pull/6060): Fix `concatMap{Single|Maybe}` null emission on success-dispose race. + +### Version 2.1.15 - June 22, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.15%7C)) + +#### API changes + +- [Pull 6026](https://github.com/ReactiveX/RxJava/pull/6026): Add `blockingSubscribe` overload with prefetch amount allowing bounded backpressure. +- [Pull 6052](https://github.com/ReactiveX/RxJava/pull/6052): Change `{PublishSubject|PublishProcessor}.subscribeActual` to `protected`. They were accidentally made public and there is no reason to call them outside of RxJava internals. + +#### Documentation changes + +- [Pull 6031](https://github.com/ReactiveX/RxJava/pull/6031): Inline `CompositeDisposable` JavaDoc. +- [Pull 6042](https://github.com/ReactiveX/RxJava/pull/6042): Fix `MulticastProcessor` JavaDoc comment. +- [Pull 6049](https://github.com/ReactiveX/RxJava/pull/6049): Make it explicit that `throttleWithTimout` is an alias of `debounce`. +- [Pull 6053](https://github.com/ReactiveX/RxJava/pull/6053): Add `Maybe` marble diagrams 06/21/a +- [Pull 6057](https://github.com/ReactiveX/RxJava/pull/6057): Use different wording on `blockingForEach()` JavaDocs. +- [Pull 6054](https://github.com/ReactiveX/RxJava/pull/6054): Expand `{X}Processor` JavaDocs by syncing with `{X}Subject` docs. + +#### Performance enhancements + +- [Pull 6021](https://github.com/ReactiveX/RxJava/pull/6021): Add full implementation for `Single.flatMapPublisher` so it doesn't batch requests. +- [Pull 6024](https://github.com/ReactiveX/RxJava/pull/6024): Dedicated `{Single|Maybe}.flatMap{Publisher|Observable}` & `andThen(Observable|Publisher)` implementations. +- [Pull 6028](https://github.com/ReactiveX/RxJava/pull/6028): Improve `Observable.takeUntil`. + +#### Bugfixes + +- [Pull 6019](https://github.com/ReactiveX/RxJava/pull/6019): Fix `Single.takeUntil`, `Maybe.takeUntil` dispose behavior. +- [Pull 5947](https://github.com/ReactiveX/RxJava/pull/5947): Fix `groupBy` eviction so that source is cancelled and reduce volatile reads. +- [Pull 6036](https://github.com/ReactiveX/RxJava/pull/6036): Fix disposed `LambdaObserver.onError` to route to global error handler. +- [Pull 6045](https://github.com/ReactiveX/RxJava/pull/6045): Fix check in `BlockingSubscriber` that would always be false due to wrong variable. + +#### Other changes + +- [Pull 6022](https://github.com/ReactiveX/RxJava/pull/6022): Add TCK for `MulticastProcessor` & `{0..1}.flatMapPublisher` +- [Pull 6029](https://github.com/ReactiveX/RxJava/pull/6029): Upgrade to Gradle 4.3.1, add `TakeUntilPerf`. +- [Pull 6033](https://github.com/ReactiveX/RxJava/pull/6033): Update & fix grammar of `DESIGN.md` + +### Version 2.1.14 - May 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.14%7C)) + +#### API changes + +- [Pull 5976](https://github.com/ReactiveX/RxJava/pull/5976): Add `Single.concatEager()`. +- [Pull 5986](https://github.com/ReactiveX/RxJava/pull/5986): Add `ConnectableObservable.refCount()` and `ConnectableFlowable.refCount()` with minimum consumer count & disconnect grace period. +- [Pull 5979](https://github.com/ReactiveX/RxJava/pull/5979): Add `Observable.throttleLatest` and `Flowable.throttleLatest()`. +- [Pull 6002](https://github.com/ReactiveX/RxJava/pull/6002): Add `MulticastProcessor`. +- [Pull 6010](https://github.com/ReactiveX/RxJava/pull/6010): Add `assertValueSetOnly` and `assertValueSequenceOnly` to `TestObserver`/`TestSubscriber`. + +#### Deprecations + +- [Pull 5982](https://github.com/ReactiveX/RxJava/pull/5982): Deprecate `getValues()` in `Subject`s/`FlowableProcessor`s to be removed in 3.x. + +#### Documentation changes + +- [Pull 5977](https://github.com/ReactiveX/RxJava/pull/5977): `Maybe`/`Single` JavaDocs; annotation cleanup. +- [Pull 5981](https://github.com/ReactiveX/RxJava/pull/5981): Improve JavaDocs of the `subscribeActual` methods. +- [Pull 5984](https://github.com/ReactiveX/RxJava/pull/5984): Add `blockingSubscribe` JavaDoc clarifications. +- [Pull 5987](https://github.com/ReactiveX/RxJava/pull/5987): Add marble diagrams to some `Single.doOnX` methods. +- [Pull 5992](https://github.com/ReactiveX/RxJava/pull/5992): `Observable` javadoc cleanup. + +#### Bugfixes + +- [Pull 5975](https://github.com/ReactiveX/RxJava/pull/5975): Fix `refCount()` connect/subscribe/cancel deadlock. +- [Pull 5978](https://github.com/ReactiveX/RxJava/pull/5978): `Flowable.take` to route post-cancel errors to plugin error handler. +- [Pull 5991](https://github.com/ReactiveX/RxJava/pull/5991): Fix `switchMap` to indicate boundary fusion. + +#### Other changes + +- [Pull 5985](https://github.com/ReactiveX/RxJava/pull/5985): Cleanup in the `Scheduler` class. +- [Pull 5996](https://github.com/ReactiveX/RxJava/pull/5996): Automatically publish the generated JavaDocs from CI. +- [Pull 5995](https://github.com/ReactiveX/RxJava/pull/5995): Implement `toString` method for some `Emitter`s. +- [Pull 6005](https://github.com/ReactiveX/RxJava/pull/6005): JavaDocs HTML formatting and whitespace cleanup. +- [Pull 6014](https://github.com/ReactiveX/RxJava/pull/6014): Fix & prevent `null` checks on primitives. + +### Version 2.1.13 - April 27, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.13%7C)) + +#### API changes + +- [Pull 5957](https://github.com/ReactiveX/RxJava/pull/5957): Add `Single.ignoreElement`, deprecate `Single.toCompletable` (will be removed in 3.0). + +#### Documentation changes + +- [Pull 5936](https://github.com/ReactiveX/RxJava/pull/5936): Fix `Completable.toMaybe()` `@return` javadoc. +- [Pull 5948](https://github.com/ReactiveX/RxJava/pull/5948): Fix `Observable` javadoc mentioning `doOnCancel` instead of `doOnDispose`. +- [Pull 5951](https://github.com/ReactiveX/RxJava/pull/5951): Update `blockingX` JavaDoc to mention wrapping of checked Exceptions. + +#### Bugfixes + +- [Pull 5952](https://github.com/ReactiveX/RxJava/pull/5952): Fixed conditional iteration breaking in `AppendOnlyLinkedArrayList.forEachWhile`. +- [Pull 5972](https://github.com/ReactiveX/RxJava/pull/5972): Fix `Observable.concatMapSingle` dropping upstream items. + +#### Other changes + +- [Pull 5930](https://github.com/ReactiveX/RxJava/pull/5930): Add `@NonNull` annotations to create methods of `Subject`s and `Processor`s. +- [Pull 5940](https://github.com/ReactiveX/RxJava/pull/5940): Allow `@SchedulerSupport` annotation on constructors. +- [Pull 5942](https://github.com/ReactiveX/RxJava/pull/5942): Removed `TERMINATED` check in `PublishSubject.onNext` and `PublishProcessor.onNext`. +- [Pull 5959](https://github.com/ReactiveX/RxJava/pull/5959): Fix some typos and grammar mistakes. + +### Version 2.1.12 - March 23, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.12%7C)) + +#### Bugfixes + +- [Pull 5928](https://github.com/ReactiveX/RxJava/pull/5928): Fix `concatMapSingle` & `concatMapMaybe` dispose-cleanup crash. + +### Version 2.1.11 - March 20, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.11%7C)) + +#### API changes + +- [Pull 5871](https://github.com/ReactiveX/RxJava/pull/5871): Add `Flowable.concatMapCompletable{DelayError}` operator. +- [Pull 5870](https://github.com/ReactiveX/RxJava/pull/5870): Add `Flowable.switchMapCompletable{DelayError}` operator. +- [Pull 5872](https://github.com/ReactiveX/RxJava/pull/5872): Add `Flowable.concatMap{Maybe,Single}{DelayError}` operators. +- [Pull 5873](https://github.com/ReactiveX/RxJava/pull/5873): Add `Flowable.switchMap{Maybe,Single}{DelayError}` operators. +- [Pull 5875](https://github.com/ReactiveX/RxJava/pull/5875): Add `Observable` `switchMapX` and `concatMapX` operators. +- [Pull 5906](https://github.com/ReactiveX/RxJava/pull/5906): Add public constructor for `TestScheduler` that takes the initial virtual time. + +#### Performance enhancements + +- [Pull 5915](https://github.com/ReactiveX/RxJava/pull/5915): Optimize `Observable.concatMapCompletable`. +- [Pull 5918](https://github.com/ReactiveX/RxJava/pull/5918): Improve the scalar source performance of `Observable.(concat|switch)Map{Completable|Single|Maybe}`. +- [Pull 5919](https://github.com/ReactiveX/RxJava/pull/5919): Add fusion to `Observable.switchMap` inner source. + +#### Documentation changes + +- [Pull 5863](https://github.com/ReactiveX/RxJava/pull/5863): Expand the documentation of the `Flowable.lift()` operator. +- [Pull 5865](https://github.com/ReactiveX/RxJava/pull/5865): Improve the JavaDoc of the other `lift()` operators. +- [Pull 5876](https://github.com/ReactiveX/RxJava/pull/5876): Add note about `NoSuchElementException` to `Single.zip()`. +- [Pull 5897](https://github.com/ReactiveX/RxJava/pull/5897): Clarify `dematerialize()` and terminal items/signals. +- [Pull 5895](https://github.com/ReactiveX/RxJava/pull/5895): Fix `buffer()` documentation to correctly describe `onError` behavior. + +#### Bugfixes + +- [Pull 5887](https://github.com/ReactiveX/RxJava/pull/5887): Fix `window(Observable|Callable)` upstream handling. +- [Pull 5888](https://github.com/ReactiveX/RxJava/pull/5888): Fix `Flowable.window(Publisher|Callable)` upstream handling. +- [Pull 5892](https://github.com/ReactiveX/RxJava/pull/5892): Fix the extra retention problem in `ReplaySubject`. +- [Pull 5900](https://github.com/ReactiveX/RxJava/pull/5900): Fix `Observable.flatMap` scalar `maxConcurrency` overflow. +- [Pull 5893](https://github.com/ReactiveX/RxJava/pull/5893): Fix `publish(-|Function)` subscriber swap possible data loss. +- [Pull 5898](https://github.com/ReactiveX/RxJava/pull/5898): Fix excess item retention in the other `replay` components. +- [Pull 5904](https://github.com/ReactiveX/RxJava/pull/5904): Fix `Flowable.singleOrError().toFlowable()` not signalling `NoSuchElementException`. +- [Pull 5883](https://github.com/ReactiveX/RxJava/pull/5883): Fix `FlowableWindowBoundary` not cancelling the upstream on a missing backpressure case, causing `NullPointerException`. + +#### Other changes + +- [Pull 5890](https://github.com/ReactiveX/RxJava/pull/5890): Added `@Nullable` annotations to subjects. +- [Pull 5886](https://github.com/ReactiveX/RxJava/pull/5886): Upgrade the algorithm of Observable.timeout(time|selector) operators. +- Coverage improvements + - [Pull 5883](https://github.com/ReactiveX/RxJava/pull/5883): Improve coverage and fix small mistakes/untaken paths in operators. + - [Pull 5889](https://github.com/ReactiveX/RxJava/pull/5889): Cleanup, coverage and related component fixes + - [Pull 5891](https://github.com/ReactiveX/RxJava/pull/5891): Improve coverage & related cleanup 03/05. + - [Pull 5905](https://github.com/ReactiveX/RxJava/pull/5905): Coverage improvements, logical fixes and cleanups 03/08. + - [Pull 5910](https://github.com/ReactiveX/RxJava/pull/5910): Improve coverage, fix operator logic 03/12. + +### Version 2.1.10 - February 24, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.10%7C)) + +#### API changes + +- [Pull 5845](https://github.com/ReactiveX/RxJava/pull/5845): Add efficient `concatWith(Single|Maybe|Completable)` overloads to `Flowable` and `Observable`. +- [Pull 5847](https://github.com/ReactiveX/RxJava/pull/5847): Add efficient `mergeWith(Single|Maybe|Completable)` overloads to `Flowable` and `Observable`. +- [Pull 5860](https://github.com/ReactiveX/RxJava/pull/5860): Add `Flowable.groupBy` overload with evicting map factory. + +#### Documentation changes + +- [Pull 5824](https://github.com/ReactiveX/RxJava/pull/5824): Improve the wording of the `share()` JavaDocs. +- [Pull 5826](https://github.com/ReactiveX/RxJava/pull/5826): Fix `Observable.blockingIterable(int)` and add `Observable.blockingLatest` marbles. +- [Pull 5828](https://github.com/ReactiveX/RxJava/pull/5828): Document size-bounded `replay` emission's item retention property. +- [Pull 5830](https://github.com/ReactiveX/RxJava/pull/5830): Reword the `just()` operator and reference other typical alternatives. +- [Pull 5834](https://github.com/ReactiveX/RxJava/pull/5834): Fix copy-paste errors in `SingleSubject` JavaDoc. +- [Pull 5837](https://github.com/ReactiveX/RxJava/pull/5837): Detail `distinct()` and `distinctUntilChanged()` in JavaDoc. +- [Pull 5841](https://github.com/ReactiveX/RxJava/pull/5841): Improve JavaDoc of `Observer`, `SingleObserver`, `MaybeObserver` and `CompletableObserver`. +- [Pull 5843](https://github.com/ReactiveX/RxJava/pull/5843): Expand the JavaDocs of the `Scheduler` API. +- [Pull 5844](https://github.com/ReactiveX/RxJava/pull/5844): Explain the properties of the `{Flowable|Observable|Single|Maybe|Completable}Emitter` interfaces in detail. +- [Pull 5848](https://github.com/ReactiveX/RxJava/pull/5848): Improve the wording of the `Maybe.fromCallable` JavaDoc. +- [Pull 5856](https://github.com/ReactiveX/RxJava/pull/5856): Add finite requirement to various collector operators' JavaDoc. + +#### Bugfixes + +- [Pull 5833](https://github.com/ReactiveX/RxJava/pull/5833): Fix `Observable.switchMap` main `onError` not disposing the current inner source. + +#### Other changes + +- [Pull 5838](https://github.com/ReactiveX/RxJava/pull/5838): Added nullability annotation for completable assembly. +- [Pull 5858](https://github.com/ReactiveX/RxJava/pull/5858): Remove unnecessary comment from `Observable.timeInterval(TimeUnit)`. + +### Version 2.1.9 - January 24, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.9%7C)) + +#### API changes + +- [Pull 5799](https://github.com/ReactiveX/RxJava/pull/5799): Add missing `{Maybe|Single}.mergeDelayError` variants. + +#### Performance improvements + +- [Pull 5790](https://github.com/ReactiveX/RxJava/pull/5790): Improve request accounting overhead in `Flowable` `retry`/`repeat`. + +#### Documentation changes + +- [Pull 5783](https://github.com/ReactiveX/RxJava/pull/5783): Fix JavaDoc wording of `onTerminateDetach`. +- [Pull 5780](https://github.com/ReactiveX/RxJava/pull/5780): Improve `BehaviorSubject` JavaDoc + related clarifications. +- [Pull 5781](https://github.com/ReactiveX/RxJava/pull/5781): Describe `merge()` error handling. +- [Pull 5785](https://github.com/ReactiveX/RxJava/pull/5785): Update `Maybe doOn{Success,Error,Complete}` JavaDoc. +- [Pull 5786](https://github.com/ReactiveX/RxJava/pull/5786): Add error handling section to `merge()` operator JavaDocs. +- [Pull 5802](https://github.com/ReactiveX/RxJava/pull/5802): Improved `XSubject` JavaDocs. +- Marble diagram fixes to `Observable`: + - [Pull 5795](https://github.com/ReactiveX/RxJava/pull/5795): More marbles 01/08-a. + - [Pull 5797](https://github.com/ReactiveX/RxJava/pull/5797): `Observable` marble fixes 01/08-b. + - [Pull 5798](https://github.com/ReactiveX/RxJava/pull/5798): `Observable.replay(Function, ...)` marble fixes. + - [Pull 5804](https://github.com/ReactiveX/RxJava/pull/5804): More `Observable` marbles, 01/10-a. + - [Pull 5805](https://github.com/ReactiveX/RxJava/pull/5805): Final planned `Observable` marble additions/fixes. + - [Pull 5816](https://github.com/ReactiveX/RxJava/pull/5816): Add `Subject` and `Processor` marbles. + +#### Bugfixes + +- [Pull 5792](https://github.com/ReactiveX/RxJava/pull/5792): Fix `flatMap` inner fused poll crash not cancelling the upstream. +- [Pull 5811](https://github.com/ReactiveX/RxJava/pull/5811): Fix `buffer(open, close)` not disposing indicators properly. + +### Version 2.1.8 - December 27, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.8%7C)) + +**Warning! Behavior change regarding handling illegal calls with `null` in `Processor`s and `Subject`s.** + +The [Reactive Streams specification](https://github.com/reactive-streams/reactive-streams-jvm#user-content-2.13) mandates that calling `onNext` and `onError` with `null` should +result in an immediate `NullPointerException` thrown from these methods. Unfortunately, this requirement was overlooked (it resulted in calls to `onError` with `NullPointerException` which meant the Processor/Subject variants entered their terminal state). + +If, for some reason, the original behavior is required, one has to call `onError` with a `NullPointerException` explicitly: + +```java +PublishSubject ps = PublishSubject.create(); + +TestObserver to = ps.test(); + +// ps.onNext(null); // doesn't work anymore + +ps.onError(new NullPointerException()); + +to.assertFailure(NullPointerException.class); +``` + +#### API changes + +- [Pull 5741](https://github.com/ReactiveX/RxJava/pull/5741): API to get distinct `Worker`s from some `Scheduler`s. +- [Pull 5734](https://github.com/ReactiveX/RxJava/pull/5734): Add `RxJavaPlugins.unwrapRunnable` to help with RxJava-internal wrappers in `Scheduler`s. +- [Pull 5753](https://github.com/ReactiveX/RxJava/pull/5753): Add `retry(times, predicate)` to `Single` & `Completable` and verify behavior across them and `Maybe`. + +#### Documentation changes + +- [Pull 5746](https://github.com/ReactiveX/RxJava/pull/5746): Improve wording and links in `package-info`s + remove unused imports. +- [Pull 5745](https://github.com/ReactiveX/RxJava/pull/5745): Add/update `Observable` marbles 11/28. +- [Commit 53d5a235](https://github.com/ReactiveX/RxJava/commit/53d5a235f63ca143c11571cd538ad927c0f8f3ad): Fix JavaDoc link in observables/package-info. +- [Pull 5755](https://github.com/ReactiveX/RxJava/pull/5755): Add marbles for `Observable` (12/06). +- [Pull 5756](https://github.com/ReactiveX/RxJava/pull/5756): Improve `autoConnect()` JavaDoc + add its marble. +- [Pull 5758](https://github.com/ReactiveX/RxJava/pull/5758): Add a couple of `@see` to `Completable`. +- [Pull 5759](https://github.com/ReactiveX/RxJava/pull/5759): Marble additions and updates (12/11) +- [Pull 5773](https://github.com/ReactiveX/RxJava/pull/5773): Improve JavaDoc of `retryWhen()` operators. +- [Pull 5778](https://github.com/ReactiveX/RxJava/pull/5778): Improve `BehaviorProcessor` JavaDoc. + +#### Bugfixes + +- [Pull 5747](https://github.com/ReactiveX/RxJava/pull/5747): Fix `TrampolineScheduler` not calling `RxJavaPlugins.onSchedule()`, add tests for all schedulers. +- [Pull 5748](https://github.com/ReactiveX/RxJava/pull/5748): Check `runnable == null` in `*Scheduler.schedule*()`. +- [Pull 5761](https://github.com/ReactiveX/RxJava/pull/5761): Fix timed exact `buffer()` calling cancel unnecessarily. +- [Pull 5760](https://github.com/ReactiveX/RxJava/pull/5760): `Subject`/`FlowableProcessor` NPE fixes, add `UnicastProcessor` TCK. + +#### Other + +- [Pull 5771](https://github.com/ReactiveX/RxJava/pull/5771): Upgrade dependency to Reactive Streams 1.0.2 +- [Pull 5766](https://github.com/ReactiveX/RxJava/pull/5766): Rename `XOnSubscribe` parameter name to `emitter` for better IDE auto-completion. + +### Version 2.1.7 - November 27, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.7%7C)) + +#### API changes + +- [Pull 5729](https://github.com/ReactiveX/RxJava/pull/5729): Implement `as()` operator on the 6 base classes - similar to `to()` but dedicated functional interface for each base class instead of just `Function`. + +#### Documentation changes + +- [Pull 5706](https://github.com/ReactiveX/RxJava/pull/5706): Remove mentions of Main thread from `Schedulers.single()` JavaDoc. +- [Pull 5709](https://github.com/ReactiveX/RxJava/pull/5709): Improve JavaDocs of `flatMapSingle` and `flatMapMaybe`. +- [Pull 5713](https://github.com/ReactiveX/RxJava/pull/5713): Add `BaseTestConsumer` `values()` and `errors()` thread-safety clarifications. +- [Pull 5717](https://github.com/ReactiveX/RxJava/pull/5717): Add period to custom scheduler use sentences in `Schedulers`. +- [Pull 5718](https://github.com/ReactiveX/RxJava/pull/5718): Add a sentence to documentation of `take()` operator about the thread `onComplete` may get signaled. +- [Pull 5738](https://github.com/ReactiveX/RxJava/pull/5738): Correct JavaDoc for `ConnectableFlowable`, `GroupedFlowable`, `FlowableAutoConnect`. +- [Pull 5740](https://github.com/ReactiveX/RxJava/pull/5740): Marbles for `Observable` `all`, `fromPublisher`, `zipArray`. + +#### Bugfixes + +- [Pull 5695](https://github.com/ReactiveX/RxJava/pull/5695): Fix `Completable.concat` to use replace (don't dispose old). +- [Pull 5715](https://github.com/ReactiveX/RxJava/pull/5715): Distinguish between sync and async dispose in `ScheduledRunnable`. +- [Pull 5743](https://github.com/ReactiveX/RxJava/pull/5743): Check `isDisposed` before emitting in `SingleFromCallable`. + +#### Other + +- [Pull 5723](https://github.com/ReactiveX/RxJava/pull/5723): Remove duplicate nullity check line in `toMap`. + +### Version 2.1.6 - October 27, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.6%7C)) + +#### API changes + +- [Pull 5649](https://github.com/ReactiveX/RxJava/pull/5649): Add `Observable.concatMapCompletable()`. +- [Pull 5655](https://github.com/ReactiveX/RxJava/pull/5655): Add `Flowable.limit()` to limit both item count and request amount. + +#### Documentation changes + +- [Pull 5648](https://github.com/ReactiveX/RxJava/pull/5648): Improve package JavaDoc of `io.reactivex` and `io.reactivex.observers`. +- [Pull 5647](https://github.com/ReactiveX/RxJava/pull/5647): Fix `subscribeWith` documentation examples. +- [Pull 5651](https://github.com/ReactiveX/RxJava/pull/5651): Update `Observable.just(2..10)` and `switchOnNextDelayError` marbles. +- [Pull 5668](https://github.com/ReactiveX/RxJava/pull/5668): Fix a misleading documentation of `Observable.singleElement()`. +- [Pull 5680](https://github.com/ReactiveX/RxJava/pull/5680): More `Observable` marble fixes. + +#### Bugfixes + +- [Pull 5669](https://github.com/ReactiveX/RxJava/pull/5669): Fix `PublishProcessor` cancel/emission overflow bug. +- [Pull 5677](https://github.com/ReactiveX/RxJava/pull/5677): Make `parallel()` a fusion-async-boundary. + +#### Other + +- [Pull 5652](https://github.com/ReactiveX/RxJava/pull/5652): Inline disposability in `Observable.concatMap(Completable)`. +- [Pull 5653](https://github.com/ReactiveX/RxJava/pull/5653): Upgrade testng to get method names to show up in gradle console when skipping, and in testng html output. +- [Pull 5661](https://github.com/ReactiveX/RxJava/pull/5661): Improve `Flowable.timeout()`. + +### Version 2.1.5 - October 5, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.5%7C)) + +#### API changes + +- [Pull 5616](https://github.com/ReactiveX/RxJava/pull/5616): Add `Single.delay` overload that delays errors. +- [Pull 5624](https://github.com/ReactiveX/RxJava/pull/5624): add `onTerminateDetach` to `Single` and `Completable`. + +#### Documentation changes + +- [Pull 5617](https://github.com/ReactiveX/RxJava/pull/5617): Fix `Observable.delay` & `Flowable.delay` javadoc. +- [Pull 5637](https://github.com/ReactiveX/RxJava/pull/5637): Fixing JavaDoc warnings. +- [Pull 5640](https://github.com/ReactiveX/RxJava/pull/5640): Additional warnings for `fromPublisher()`. + +#### Bugfixes + +- No bugs were reported. + +#### Other + +- [Pull 5615](https://github.com/ReactiveX/RxJava/pull/5615): Add missing license headers. +- [Pull 5623](https://github.com/ReactiveX/RxJava/pull/5623): Fix incorrect error message in `SubscriptionHelper.setOnce` +- [Pull 5633](https://github.com/ReactiveX/RxJava/pull/5633): Upgrade to Gradle 4.2.1, remove nebula plugin, replace it with custom release logic. + + +### Version 2.1.4 - September 22, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.4%7C)) + +#### API changes + +- [Pull 5568](https://github.com/ReactiveX/RxJava/pull/5568): Add `BaseTestConsumer.assertValuesOnly`. +- [Pull 5582](https://github.com/ReactiveX/RxJava/pull/5582): Add `Maybe.switchIfEmpty(Single)`. +- [Pull 5590](https://github.com/ReactiveX/RxJava/pull/5590): Add `LambdaConsumerIntrospection` for detecting the lack of `onError` callback in `subscribe()` calls. + +#### Documentation changes + +- [Pull 5578](https://github.com/ReactiveX/RxJava/pull/5578): Add `NullPointerException` comment to some `Single` operators. +- [Pull 5600](https://github.com/ReactiveX/RxJava/pull/5600): Updating JavaDoc with correct return types of `Single` in some `Observable` operators. + +#### Bugfixes + +- [Pull 5560](https://github.com/ReactiveX/RxJava/pull/5560): Fix `Observable.combineLatestDelayError` sync initial error not emitting. +- [Pull 5594](https://github.com/ReactiveX/RxJava/pull/5594): Fix `BaseTestConsumer.assertValueSequence` reversed error message. +- [Pull 5609](https://github.com/ReactiveX/RxJava/pull/5609): Fix `Observable.concatMapEager` queueing of source items. + +#### Other + +- [Pull 5586](https://github.com/ReactiveX/RxJava/pull/5586): fix `Single.timeout` unnecessary dispose calls. + + +### Version 2.1.3 - August 15, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.3%7C)) + +#### Dependency updates + +The [Reactive-Streams](https://github.com/reactive-streams/reactive-streams-jvm) dependency has been updated to version [1.0.1](https://github.com/reactive-streams/reactive-streams-jvm/releases/tag/v1.0.1). This new version contains documentation changes and TCK (Test Compatibility Kit) fixes that doesn't affect RxJava's frontend. Other libraries that were using version 1.0.0 should have no issue running with 1.0.1. + +#### JDK 9 compatibility + +RxJava 2 from now on is compatible with JDK 9, verified in a [separate project](https://github.com/akarnokd/RxJava2_9) whenever a new (non-trivial) update of the JDK or RxJava happens. + +Compatibility means the RxJava 2 source code compiles with JDK 9 (targets 6, 8 and 9) and the unit tests pass. + +#### API changes + +- [Pull 5529](https://github.com/ReactiveX/RxJava/pull/5529): Add `assertValueAt(int, value)` to `TestObserver`/`TestConsumer`. + +#### Documentation changes + +- [Pull 5524](https://github.com/ReactiveX/RxJava/pull/5524): Add/update `Observable` marbles (07/30) via [Issue 5319 comments](https://github.com/ReactiveX/RxJava/issues/5319#issuecomment-318864476). +- [Pull 5552](https://github.com/ReactiveX/RxJava/pull/5552): Fix a typo in `Schedulers`. + +#### Bugfixes + +- [Pull 5517](https://github.com/ReactiveX/RxJava/pull/5517): Add missing `null` check to fused `Observable.fromCallable`. + +#### Other + +- [Pull 5546](https://github.com/ReactiveX/RxJava/pull/5546): Upgrade Reactive-Streams dependency to 1.0.1 + +### Version 2.1.2 - July 23, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.2%7C)) + +#### Documentation changes + +- [Pull 5432](https://github.com/ReactiveX/RxJava/pull/5432): Fix/clarify the `Observable` class' javadoc. +- [Pull 5444](https://github.com/ReactiveX/RxJava/pull/5444): Fix wording in `Async` and `Publish` processors javadoc. +- [Pull 5413](https://github.com/ReactiveX/RxJava/pull/5413): Add empty source clauses to javadocs of `combineLatest` operators accepting unspecified number of sources. +- [Pull 5465](https://github.com/ReactiveX/RxJava/pull/5465): Fix wording of toList, fix a/an in `subscribeOn`. +- [Pull 5476](https://github.com/ReactiveX/RxJava/pull/5476): Fix Javadoc for `Flowable` and `Observable` reduce. +- [Pull 5478](https://github.com/ReactiveX/RxJava/pull/5478): Corrected return type in doc for `F.reduce(seed, reducer)`, `F.reduceWith(seedSupplier, reducer)` and `O.reduce(seed, reducer)`. +- [Pull 5486](https://github.com/ReactiveX/RxJava/pull/5486): Small note on `Maybe.defaultIfEmpty` regarding `toSingle`. + +#### Bugfixes + +- [Pull 5434](https://github.com/ReactiveX/RxJava/pull/5434): Fix time bounded `ReplaySubject.getValue()` inconsistency with `getValues()` on old items. +- [Pull 5440](https://github.com/ReactiveX/RxJava/pull/5440): `concat` to report `isDisposed` consistently with termination. +- [Pull 5441](https://github.com/ReactiveX/RxJava/pull/5441): Fix periodic scheduler purging config not honored. +- [Pull 5494](https://github.com/ReactiveX/RxJava/pull/5494): Fix `FlowableWithLatestFrom` not requesting more when the other hasn't emitted yet. +- [Pull 5493](https://github.com/ReactiveX/RxJava/pull/5493): Fix `ReplayProcessor` backpressure and `NotificationLite` emission bug. +- [Pull 5507](https://github.com/ReactiveX/RxJava/pull/5507): Fix GC nepotism in `SpscLinkedArrayQueue`. +- [Pull 5511](https://github.com/ReactiveX/RxJava/pull/5511): Remove unnecessary generic type parameter for the timed `Single.delaySubscription` methods. + +#### Other + +- [Pull 5447](https://github.com/ReactiveX/RxJava/pull/5447): Remove `@NonNull` annotation in `Consumer` method parameter. +- [Pull 5449](https://github.com/ReactiveX/RxJava/pull/5449): Remove the `@NonNull` annotation from `Function`. +- [Commit 4d8f008c](https://github.com/ReactiveX/RxJava/commit/4d8f008cb6823730b5e25fea559905a811d8ce32): add missing 'the' to the changed sentences of Pull 5413 +- [Pull 5460](https://github.com/ReactiveX/RxJava/pull/5460): Fix Javadoc mistakes and some style. +- [Pull 5466](https://github.com/ReactiveX/RxJava/pull/5466): Use a mutable field in `FlowableTimeoutTimed` instead of an `AtomicReference`. +- [Commit 5d2e8fb4](https://github.com/ReactiveX/RxJava/commit/5d2e8fb4363f18c5cbb247e2d4c6ed1c71527128): Fix `Schedulers.io()` javadoc `{link` missing the `@` symbol. +- [Pull 5495](https://github.com/ReactiveX/RxJava/pull/5495): Make `withLatestFrom` conditional subscriber, test cold consumption. + ### Version 2.1.1 - June 21, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.1.1%7C)) #### Notable changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62f2249abf..93050459df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ When submitting code, please make every effort to follow existing conventions an ## License -By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/ReactiveX/RxJava/blob/master/LICENSE +By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/ReactiveX/RxJava/blob/2.x/LICENSE All files are released with the Apache 2.0 license. diff --git a/DESIGN.md b/DESIGN.md index 29d4d332f9..5480b39948 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -38,12 +38,12 @@ Producer is in charge. Consumer has to do whatever it needs to keep up. Examples: -- `Observable` (RxJS, Rx.Net, RxJava v1.x without backpressure, RxJava v2) -- Callbacks (the producer calls the function at its convenience) -- IRQ, mouse events, IO interrupts -- 2.x `Flowable` (with `request(n)` credit always granted faster or in larger quantity than producer) -- Reactive Streams `Publisher` (with `request(n)` credit always granted faster or in larger quantity than producer) -- Java 9 `Flow.Publisher` (with `request(n)` credit always granted faster than or in larger quantity producer) +- `Observable` (RxJS, Rx.Net, RxJava v1.x without backpressure, RxJava v2). +- Callbacks (the producer calls the function at its convenience). +- IRQ, mouse events, IO interrupts. +- 2.x `Flowable` (with `request(n)` credit always granted faster or in larger quantity than producer). +- Reactive Streams `Publisher` (with `request(n)` credit always granted faster or in larger quantity than producer). +- Java 9 `Flow.Publisher` (with `request(n)` credit always granted faster than or in larger quantity than producer). ##### Synchronous Interactive/Pull @@ -52,11 +52,11 @@ Consumer is in charge. Producer has to do whatever it needs to keep up. Examples: -- `Iterable` -- 2.x/1.x `Observable` (without concurrency, producer and consumer on the same thread) -- 2.x `Flowable` (without concurrency, producer and consumer on the same thread) -- Reactive Streams `Publisher` (without concurrency, producer and consumer on the same thread) -- Java 9 `Flow.Publisher` (without concurrency, producer and consumer on the same thread) +- `Iterable`. +- 2.x/1.x `Observable` (without concurrency, producer and consumer on the same thread). +- 2.x `Flowable` (without concurrency, producer and consumer on the same thread). +- Reactive Streams `Publisher` (without concurrency, producer and consumer on the same thread). +- Java 9 `Flow.Publisher` (without concurrency, producer and consumer on the same thread). ##### Async Pull (Async Interactive) @@ -65,24 +65,24 @@ Consumer requests data when it wishes, and the data is then pushed when the prod Examples: -- `Future` & `Promise` -- `Single` (lazy `Future`) -- 2.x `Flowable` -- Reactive Streams `Publisher` -- Java 9 `Flow.Publisher` -- 1.x `Observable` (with backpressure) -- `AsyncEnumerable`/`AsyncIterable` +- `Future` & `Promise`. +- `Single` (lazy `Future`). +- 2.x `Flowable`. +- Reactive Streams `Publisher`. +- Java 9 `Flow.Publisher`. +- 1.x `Observable` (with backpressure). +- `AsyncEnumerable`/`AsyncIterable`. There is an overhead (performance and mental) for achieving this, which is why we also have the 2.x `Observable` without backpressure. ##### Flow Control -Flow control is any mitigation strategies that a consumer applies to reduce the flow of data. +Flow control is any mitigation strategy that a consumer applies to reduce the flow of data. Examples: -- Controlling the production of data, such as with `Iterator.next` or `Subscription.request(n)` +- Controlling the production of data, such as with `Iterator.next` or `Subscription.request(n)`. - Preventing the delivery of data, such as buffer, drop, sample/throttle, and debounce. @@ -112,14 +112,14 @@ Stream that supports async and synchronous push. It does *not* support interacti Usable for: -- sync or async -- push -- 0, 1, many or infinite items +- Sync or async. +- Push. +- 0, 1, many or infinite items. Flow control support: -- buffering, sampling, throttling, windowing, dropping, etc -- temporal and count-based strategies +- Buffering, sampling, throttling, windowing, dropping, etc. +- Temporal and count-based strategies. *Type Signature* @@ -147,23 +147,23 @@ Stream that supports async and synchronous push and pull. It supports interactiv Usable for: -- pull sources -- push Observables with backpressure strategy (ie. `Observable.toFlowable(onBackpressureStrategy)`) -- sync or async -- 0, 1, many or infinite items +- Pull sources. +- Push Observables with backpressure strategy (i.e. `Observable.toFlowable(onBackpressureStrategy)`). +- Sync or async. +- 0, 1, many or infinite items. Flow control support: -- buffering, sampling, throttling, windowing, dropping, etc -- temporal and count-based strategies -- `request(n)` consumer demand signal - - for pull-based sources, this allows batched "async pull" - - for push-based sources, this allows backpressure signals to conditionally apply strategies (i.e. drop, first, buffer, sample, fail, etc) +- Buffering, sampling, throttling, windowing, dropping, etc. +- Temporal and count-based strategies. +- `request(n)` consumer demand signal: + - For pull-based sources, this allows batched "async pull". + - For push-based sources, this allows backpressure signals to conditionally apply strategies (i.e. drop, first, buffer, sample, fail, etc.). -You get a flowable from: +You get a `Flowable` from: -- Converting a Observable with a backpressure strategy -- Create from sync/async OnSubscribe API (which participate in backpressure semantics) +- Converting a Observable with a backpressure strategy. +- Create from sync/async `onSubscribe` API (which participate in backpressure semantics). *Type Signature* @@ -191,14 +191,14 @@ Lazy representation of a single response (lazy equivalent of `Future`/`Promise`) Usable for: -- pull sources -- push sources being windowed or flow controlled (such as `window(1)` or `take(1)`) -- sync or async -- 1 item +- Pull sources. +- Push sources being windowed or flow controlled (such as `window(1)` or `take(1)`). +- Sync or async. +- 1 item. Flow control: -- Not applicable (don't subscribe if the single response is not wanted) +- Not applicable (don't subscribe if the single response is not wanted). *Type Signature* @@ -219,15 +219,15 @@ interface SingleSubscriber { ##### Completable -Lazy representation of a unit of work that can complete or fail +Lazy representation of a unit of work that can complete or fail. - Semantic equivalent of `Observable.empty().doOnSubscribe()`. - Alternative for scenarios often represented with types such as `Single` or `Observable`. Usable for: -- sync or async -- 0 items +- Sync or async. +- 0 items. *Type Signature* @@ -325,9 +325,9 @@ In the addition of the previous rules, an operator for `Flowable`: ### Creation -Unlike RxJava 1.x, 2.x base classes are to be abstract, stateless and generally no longer wrap an `OnSubscribe` callback - this saves allocation in assembly time without limiting the expressiveness. Operator methods and standard factories still live as final on the base classes. +Unlike RxJava 1.x, 2.x base classes are to be abstract, stateless and generally no longer wrap an `onSubscribe` callback - this saves allocation in assembly time without limiting the expressiveness. Operator methods and standard factories still live as final on the base classes. -Instead of the indirection of an `OnSubscribe` and `lift`, operators are to be implemented by extending the base classes. For example, the `map` +Instead of the indirection of an `onSubscribe` and `lift`, operators are to be implemented by extending the base classes. For example, the `map` operator will look like this: ```java @@ -353,9 +353,9 @@ public final class FlowableMap extends Flowable { } ``` -Since Java still doesn't have extension methods, "adding" more operators can only happen through helper methods such as `lift(C -> C)` and `compose(R -> P)` where `C` is the default consumer type (i.e., `rs.Subscriber`), `R` is the base type (i.e., `Flowable`) and `P` is the base interface (i.e., `rs.Publisher`). As before, the library itself may gain or lose standard operators and/or overloads through the same community process. +Since Java still doesn't have extension methods, "adding" more operators can only happen through helper methods such as `lift(C -> C)` and `compose(R -> P)` where `C` is the default consumer type (i.e. `rs.Subscriber`), `R` is the base type (i.e. `Flowable`) and `P` is the base interface (i.e. `rs.Publisher`). As before, the library itself may gain or lose standard operators and/or overloads through the same community process. -In concert, `create(OnSubscribe)` will not be available; standard operators extend the base types directly. The conversion of other RS-based libraries will happen through the `Flowable.wrap(Publisher)` static method. +In concert, `create(onSubscribe)` will not be available; standard operators extend the base types directly. The conversion of other RS-based libraries will happen through the `Flowable.wrap(Publisher)` static method. (*The unfortunate effect of `create` in 1.x was the ignorance of the Observable contract and beginner's first choice as an entry point. We can't eliminate this path since `rs.Publisher` is a single method functional interface that can be implemented just as badly.*) @@ -363,26 +363,26 @@ Therefore, new standard factory methods will try to address the common entry poi The `Flowable` will contain the following `create` methods: - - `create(SyncGenerator)`: safe, synchronous generation of signals, one-by-one - - `create(AsyncOnSubscribe)`: batch-create signals based on request patterns - - `create(Consumer>)`: relay multiple values or error from multi-valued reactive-sources (i.e., button-clicks) while also give flow control options right there (buffer, drop, error, etc.). - - `createSingle(Consumer>)`: relay a single value or error from other reactive sources (i.e., addListener callbacks) - - `createEmpty(Consumer)`: signal a completion or error from valueless reactive sources + - `create(SyncGenerator)`: safe, synchronous generation of signals, one-by-one. + - `create(AsyncOnSubscribe)`: batch-create signals based on request patterns. + - `create(Consumer>)`: relay multiple values or error from multi-valued reactive-sources (i.e. button-clicks) while also give flow control options right there (buffer, drop, error, etc.). + - `createSingle(Consumer>)`: relay a single value or error from other reactive sources (i.e. addListener callbacks). + - `createEmpty(Consumer)`: signal a completion or error from valueless reactive sources. The `Observable` will contain the following `create` methods: - - `create(SyncGenerator)`: safe, synchronous generation of signals, one-by-one - - `create(Consumer>)`: relay multiple values or error from multi-valued reactive-sources (i.e., button-clicks) while also give flow control options right there (buffer, drop, error, etc.). - - `createSingle(Consumer>)`: relay a single value or error from other reactive sources (i.e., addListener callbacks) - - `createEmpty(Consumer)`: signal a completion or error from valueless reactive sources + - `create(SyncGenerator)`: safe, synchronous generation of signals, one-by-one. + - `create(Consumer>)`: relay multiple values or error from multi-valued reactive-sources (i.e. button-clicks) while also give flow control options right there (buffer, drop, error, etc.). + - `createSingle(Consumer>)`: relay a single value or error from other reactive sources (i.e. addListener callbacks). + - `createEmpty(Consumer)`: signal a completion or error from valueless reactive sources. The `Single` will contain the following `create` method: - - `create(Consumer>)`: relay a single value or error from other reactive sources (i.e., addListener callbacks) + - `create(Consumer>)`: relay a single value or error from other reactive sources (i.e. addListener callbacks). The `Completable` will contain the following `create` method: - - `create(Consumer)`: signal a completion or error from valueless reactive sources + - `create(Consumer)`: signal a completion or error from valueless reactive sources. The first two `create` methods take an implementation of an interface which provides state and the generator methods: @@ -509,10 +509,10 @@ There are two main levels of operator fusion: *macro* and *micro*. Macro fusion deals with the higher level view of the operators, their identity and their combination (mostly in the form of subsequence). This is partially an internal affair of the operators, triggered by the downstream operator and may work with several cases. Given an operator application pair `a().b()` where `a` could be a source or an intermediate operator itself, when the application of `b` happens in assembly time, the following can happen: - - `b` identifies `a` and decides to not apply itself. Example: `empty().flatMap()` is functionally a no-op + - `b` identifies `a` and decides to not apply itself. Example: `empty().flatMap()` is functionally a no-op. - `b` identifies `a` and decides to apply a different, conventional operator. Example: `just().subscribeOn()` is turned into `just().observeOn()`. - `b` decides to apply a new custom operator, combining and inlining existing behavior. Example: `just().subscribeOn()` internally goes to `ScalarScheduledPublisher`. - - `a` is `b` and the two operator's parameter set can be combined into a single application. Example: `filter(p1).filter(p2)` combined into `filter(p1 && p2)` + - `a` is `b` and the two operator's parameter set can be combined into a single application. Example: `filter(p1).filter(p2)` combined into `filter(p1 && p2)`. Participating in the macro-fusion externally is possible by implementing a marker interface when extending `Flowable`. Two kinds of interfaces are available: @@ -528,19 +528,19 @@ interface ScalarCallable extends java.util.Callable { `ScalarCallable` is also `Callable` and thus its value can be extracted practically anytime. For convenience (and for sense), `ScalarCallable` overrides and hides the superclass' `throws Exception` clause - throwing during assembly time is likely unreasonable for scalars. -Since Reactive-Streams doesn't allow `null`s in the value flow, we have the opportunity to define `ScalarCallable`s and `Callable`s returning `null` should be considered as an empty source - allowing operators to dispatch on the type `Callable` first then branch on the nullness of `call()`. +Since Reactive Streams doesn't allow `null`s in the value flow, we have the opportunity to define `ScalarCallable`s and `Callable`s returning `null` should be considered as an empty source - allowing operators to dispatch on the type `Callable` first then branch on the nullness of `call()`. Interoperating with other libraries, at this level is possible. Reactor-Core uses the same pattern and the two libraries can work with each other's `Publisher+Callable` types. Unfortunately, this means subscription-time only fusion as `ScalarCallable`s live locally in each library. ##### Micro-fusion -Micro-fusion goes a step deeper and tries to reuse internal structures, mostly queues, in operator pairs, saving on allocation and sometimes on atomic operations. It's property is that, in a way, subverts the standard Reactive-Streams protocol between subsequent operators that both support fusion. However, from the outside world's view, they still work according to the RS protocol. +Micro-fusion goes a step deeper and tries to reuse internal structures, mostly queues, in operator pairs, saving on allocation and sometimes on atomic operations. It's property is that, in a way, subverts the standard Reactive Streams protocol between subsequent operators that both support fusion. However, from the outside world's view, they still work according to the RS protocol. Currently, two main kinds of micro-fusion opportunities are available. ###### 1) Conditional Subscriber -This extends the RS `Subscriber`interface with an extra method: `boolean tryOnNext(T value)` and can help avoiding small request amounts in case an operator didn't forward but dropped the value. The canonical use is for the `filter()` operator where if the predicate returns false, the operator has to request 1 from upstream (since the downstream doesn't know there was a value dropped and thus not request itself). Operators wanting to participate in this fusion have to implement and subscribe with an extended Subscriber interface: +This extends the RS `Subscriber`interface with an extra method: `boolean tryOnNext(T value)` and can help avoiding small request amounts in case an operator didn't forward but dropped the value. The canonical use is for the `filter()` operator where if the predicate returns false, the operator has to request 1 from upstream (since the downstream doesn't know there was a value dropped and thus not request itself). Operators wanting to participate in this fusion have to implement and subscribe with an extended `Subscriber` interface: ```java interface ConditionalSubscriber { @@ -562,9 +562,9 @@ protected void subscribeActual(Subscriber s) { ###### 2) Queue-fusion -The second category is when two (or more) operators share the same underlying queue and each append activity at the exit point (i.e., poll()) of the queue. This can work in two modes: synchronous and asynchronous. +The second category is when two (or more) operators share the same underlying queue and each append activity at the exit point (i.e. `poll()`) of the queue. This can work in two modes: synchronous and asynchronous. -In synchronous mode, the elements of the sequence is already available (i.e., a fixed `range()` or `fromArray()`, or can be synchronously calculated in a pull fashion in `fromIterable`. In this mode, the requesting and regular onError-path is bypassed and is forbidden. Sources have to return null from `pull()` and false from `isEmpty()` if they have no more values and throw from these methods if they want to indicate an exceptional case. +In synchronous mode, the elements of the sequence is already available (i.e. a fixed `range()` or `fromArray()`, or can be synchronously calculated in a pull fashion in `fromIterable`. In this mode, the requesting and regular onError-path is bypassed and is forbidden. Sources have to return null from `pull()` and false from `isEmpty()` if they have no more values and throw from these methods if they want to indicate an exceptional case. In asynchronous mode, elements may become available at any time, therefore, `pull` returning null, as with regular queue-drain, is just the indication of temporary lack of source values. Completion and error still has to go through `onComplete` and `onError` as usual, requesting still happens as usual but when a value is available in the shared queue, it is indicated by an `onNext(null)` call. This can trigger a chain of `drain` calls without moving values in or out of different queues. @@ -588,10 +588,10 @@ For performance, the mode is an integer bitflags setup, called early during subs Since RxJava 2.x is still JDK 6 compatible, the `QueueSubscription` can't itself default unnecessary methods and implementations are required to throw `UnsupportedOperationException` for `Queue` methods other than the following: - - `poll()` - - `isEmpty()` - - `clear()` - - `size()` + - `poll()`. + - `isEmpty()`. + - `clear()`. + - `size()`. Even though other modern libraries also define this interface, they live in local packages and thus non-reusable without dragging in the whole library. Therefore, until externalized and standardized, cross-library micro-fusion won't happen. diff --git a/HEADER b/HEADER new file mode 100644 index 0000000000..3949e0b453 --- /dev/null +++ b/HEADER @@ -0,0 +1,10 @@ +Copyright (c) 2016-present, RxJava Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in +compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is +distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See +the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE b/LICENSE index 7f8ced0d1f..d645695673 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2012 Netflix, Inc. + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 82adc7bfac..21e4679a44 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,11 @@ RxJava is a Java VM implementation of [Reactive Extensions](http://reactivex.io) It extends the [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) to support sequences of data/events and adds operators that allow you to compose sequences together declaratively while abstracting away concerns about things like low-level threading, synchronization, thread-safety and concurrent data structures. -#### Version 1.x ([Javadoc](http://reactivex.io/RxJava/1.x/javadoc/)) - -Looking for version 1.x? Jump [to the 1.x branch](https://github.com/ReactiveX/RxJava/tree/1.x). - -Timeline plans for the 1.x line: - - - **June 1, 2017** - feature freeze (no new operators), only bugfixes - - **March 31, 2018** - end of life, no further development +:warning: The 2.x version is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. #### Version 2.x ([Javadoc](http://reactivex.io/RxJava/2.x/javadoc/)) -- single dependency: [Reactive-Streams](https://github.com/reactive-streams/reactive-streams-jvm) +- single dependency: [Reactive Streams](https://github.com/reactive-streams/reactive-streams-jvm) - continued support for Java 6+ & [Android](https://github.com/ReactiveX/RxAndroid) 2.3+ - performance gains through design changes learned through the 1.x cycle and through [Reactive-Streams-Commons](https://github.com/reactor/reactive-streams-commons) research project. - Java 8 lambda-friendly API @@ -31,14 +24,25 @@ Version 2.x and 1.x will live side-by-side for several years. They will have dif See the differences between version 1.x and 2.x in the wiki article [What's different in 2.0](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0). Learn more about RxJava in general on the Wiki Home. +#### Version 1.x + +The [1.x version](https://github.com/ReactiveX/RxJava/tree/1.x) is end-of-life as of **March 31, 2018**. No further development, support, maintenance, PRs and updates will happen. The [Javadoc]([Javadoc](http://reactivex.io/RxJava/1.x/javadoc/)) of the very last version, **1.3.8**, will remain accessible. + ## Getting started +### Setting up the dependency + The first step is to include RxJava 2 into your project, for example, as a Gradle compile dependency: ```groovy -compile "io.reactivex.rxjava2:rxjava:2.x.y" +implementation "io.reactivex.rxjava2:rxjava:2.x.y" ``` +(Please replace `x` and `y` with the latest version numbers: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava2/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava2/rxjava) +) + +### Hello World + The second is to write the **Hello World** program: ```java @@ -66,13 +70,93 @@ Flowable.just("Hello world") }); ``` +### Base classes + RxJava 2 features several base classes you can discover operators on: - - `io.reactivex.Flowable` : 0..N flows, supporting Reactive-Streams and backpressure - - `io.reactivex.Observable`: 0..N flows, no backpressure - - `io.reactivex.Single`: a flow of exactly 1 item or an error - - `io.reactivex.Completable`: a flow without items but only a completion or error signal - - `io.reactivex.Maybe`: a flow with no items, exactly one item or an error + - [`io.reactivex.Flowable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html): 0..N flows, supporting Reactive Streams and backpressure + - [`io.reactivex.Observable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html): 0..N flows, no backpressure, + - [`io.reactivex.Single`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Single.html): a flow of exactly 1 item or an error, + - [`io.reactivex.Completable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html): a flow without items but only a completion or error signal, + - [`io.reactivex.Maybe`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Maybe.html): a flow with no items, exactly one item or an error. + +### Some terminology + +#### Upstream, downstream + +The dataflows in RxJava consist of a source, zero or more intermediate steps followed by a data consumer or combinator step (where the step is responsible to consume the dataflow by some means): + +```java +source.operator1().operator2().operator3().subscribe(consumer); + +source.flatMap(value -> source.operator1().operator2().operator3()); +``` + +Here, if we imagine ourselves on `operator2`, looking to the left towards the source, is called the **upstream**. Looking to the right towards the subscriber/consumer, is called the **downstream**. This is often more apparent when each element is written on a separate line: + +```java +source + .operator1() + .operator2() + .operator3() + .subscribe(consumer) +``` + +#### Objects in motion + +In RxJava's documentation, **emission**, **emits**, **item**, **event**, **signal**, **data** and **message** are considered synonyms and represent the object traveling along the dataflow. + +#### Backpressure + +When the dataflow runs through asynchronous steps, each step may perform different things with different speed. To avoid overwhelming such steps, which usually would manifest itself as increased memory usage due to temporary buffering or the need for skipping/dropping data, a so-called backpressure is applied, which is a form of flow control where the steps can express how many items are they ready to process. This allows constraining the memory usage of the dataflows in situations where there is generally no way for a step to know how many items the upstream will send to it. + +In RxJava, the dedicated `Flowable` class is designated to support backpressure and `Observable` is dedicated for the non-backpressured operations (short sequences, GUI interactions, etc.). The other types, `Single`, `Maybe` and `Completable` don't support backpressure nor should they; there is always room to store one item temporarily. + +#### Assembly time + +The preparation of dataflows by applying various intermediate operators happens in the so-called **assembly time**: + +```java +Flowable flow = Flowable.range(1, 5) +.map(v -> v * v) +.filter(v -> v % 3 == 0) +; +``` + +At this point, the data is not flowing yet and no side-effects are happening. + +#### Subscription time + +This is a temporary state when `subscribe()` is called on a flow that establishes the chain of processing steps internally: + +```java +flow.subscribe(System.out::println) +```` + +This is when the **subscription side-effects** are triggered (see `doOnSubscribe`). Some sources block or start emitting items right away in this state. + +#### Runtime + +This is the state when the flows are actively emitting items, errors or completion signals: + +```java + +Observable.create(emitter -> { + while (!emitter.isDisposed()) { + long time = System.currentTimeMillis(); + emitter.onNext(time); + if (time % 2 != 0) { + emitter.onError(new IllegalStateException("Odd millisecond!")); + break; + } + } +}) +.subscribe(System.out::println, Throwable::printStackTrace); +``` + +Practically, this is when the body of the given example above executes. + +### Simple background computation One of the common use cases for RxJava is to run some computation, network request on a background thread and show the results (or error) on the UI thread: @@ -109,9 +193,22 @@ Thread.sleep(2000); Typically, you can move computations or blocking IO to some other thread via `subscribeOn`. Once the data is ready, you can make sure they get processed on the foreground or GUI thread via `observeOn`. -RxJava operators don't work with `Thread`s or `ExecutorService`s directly but with so called `Scheduler`s that abstract away sources of concurrency behind an uniform API. RxJava 2 features several standard schedulers accessible via `Schedulers` utility class. These are available on all JVM platforms but some specific platforms, such as Android, have their own typical `Scheduler`s defined: `AndroidSchedulers.mainThread()`, `SwingScheduler.instance()` or `JavaFXSchedulers.gui()`. +### Schedulers + +RxJava operators don't work with `Thread`s or `ExecutorService`s directly but with so called `Scheduler`s that abstract away sources of concurrency behind a uniform API. RxJava 2 features several standard schedulers accessible via `Schedulers` utility class. -The `Thread.sleep(2000);` at the end is no accident. In RxJava the default `Scheduler`s run on daemon threads, which means once the Java main thread exits, they all get stopped and background computations may never happen. Sleeping for some time in this example situations let's you see the output of the flow on the console with time to spare. +- `Schedulers.computation()`: Run computation intensive work on a fixed number of dedicated threads in the background. Most asynchronous operator use this as their default `Scheduler`. +- `Schedulers.io()`: Run I/O-like or blocking operations on a dynamically changing set of threads. +- `Schedulers.single()`: Run work on a single thread in a sequential and FIFO manner. +- `Schedulers.trampoline()`: Run work in a sequential and FIFO manner in one of the participating threads, usually for testing purposes. + +These are available on all JVM platforms but some specific platforms, such as Android, have their own typical `Scheduler`s defined: `AndroidSchedulers.mainThread()`, `SwingScheduler.instance()` or `JavaFXSchedulers.gui()`. + +In addition, there is option to wrap an existing `Executor` (and its subtypes such as `ExecutorService`) into a `Scheduler` via `Schedulers.from(Executor)`. This can be used, for example, to have a larger but still fixed pool of threads (unlike `computation()` and `io()` respectively). + +The `Thread.sleep(2000);` at the end is no accident. In RxJava the default `Scheduler`s run on daemon threads, which means once the Java main thread exits, they all get stopped and background computations may never happen. Sleeping for some time in this example situations lets you see the output of the flow on the console with time to spare. + +### Concurrency within a flow Flows in RxJava are sequential in nature split into processing stages that may run **concurrently** with each other: @@ -124,6 +221,8 @@ Flowable.range(1, 10) This example flow squares the numbers from 1 to 10 on the **computation** `Scheduler` and consumes the results on the "main" thread (more precisely, the caller thread of `blockingSubscribe`). However, the lambda `v -> v * v` doesn't run in parallel for this flow; it receives the values 1 to 10 on the same computation thread one after the other. +### Parallel processing + Processing the numbers 1 to 10 in parallel is a bit more involved: ```java @@ -133,39 +232,257 @@ Flowable.range(1, 10) .subscribeOn(Schedulers.computation()) .map(w -> w * w) ) -.blockingSubscribe(System.out::println); + .blockingSubscribe(System.out::println); ``` -Practically, paralellism in RxJava means running independent flows and merging their results back into a single flow. The operator `flatMap` does this by first mapping each number from 1 to 10 into its own individual `Flowable`, runs them and merges the computed squares. +Practically, parallelism in RxJava means running independent flows and merging their results back into a single flow. The operator `flatMap` does this by first mapping each number from 1 to 10 into its own individual `Flowable`, runs them and merges the computed squares. + +Note, however, that `flatMap` doesn't guarantee any order and the end result from the inner flows may end up interleaved. There are alternative operators: + + - `concatMap` that maps and runs one inner flow at a time and + - `concatMapEager` which runs all inner flows "at once" but the output flow will be in the order those inner flows were created. -Starting from 2.0.5, there is an *experimental* operator `parallel()` and type `ParallelFlowable` that helps achieve the same parallel processing pattern: +Alternatively, the `Flowable.parallel()` operator and the `ParallelFlowable` type help achieve the same parallel processing pattern: ```java Flowable.range(1, 10) -.parallel() -.runOn(Schedulers.computation()) -.map(v -> v * v) -.sequential() -.blockingSubscribe(System.out::println); + .parallel() + .runOn(Schedulers.computation()) + .map(v -> v * v) + .sequential() + .blockingSubscribe(System.out::println); ``` +### Dependent sub-flows + `flatMap` is a powerful operator and helps in a lot of situations. For example, given a service that returns a `Flowable`, we'd like to call another service with values emitted by the first service: ```java Flowable inventorySource = warehouse.getInventoryAsync(); -inventorySource.flatMap(inventoryItem -> - erp.getDemandAsync(inventoryItem.getId()) - .map(demand - -> System.out.println("Item " + inventoryItem.getName() + " has demand " + demand)); +inventorySource + .flatMap(inventoryItem -> erp.getDemandAsync(inventoryItem.getId()) + .map(demand -> "Item " + inventoryItem.getName() + " has demand " + demand)) + .subscribe(System.out::println); +``` + +### Continuations + +Sometimes, when an item has become available, one would like to perform some dependent computations on it. This is sometimes called **continuations** and, depending on what should happen and what types are involved, may involve various operators to accomplish. + +#### Dependent + +The most typical scenario is to given a value, invoke another service, await and continue with its result: + +```java +service.apiCall() +.flatMap(value -> service.anotherApiCall(value)) +.flatMap(next -> service.finalCall(next)) +``` + +It is often the case also that later sequences would require values from earlier mappings. This can be achieved by moving the outer `flatMap` into the inner parts of the previous `flatMap` for example: + +```java +service.apiCall() +.flatMap(value -> + service.anotherApiCall(value) + .flatMap(next -> service.finalCallBoth(value, next)) ) -.subscribe(); ``` -Note, however, that `flatMap` doesn't guarantee any order and the end result from the inner flows may end up interleaved. There are alternative operators: +Here, the original `value` will be available inside the inner `flatMap`, courtesy of lambda variable capture. - - `concatMap` that maps and runs one inner flow at a time and - - `concatMapEager` which runs all inner flows "at once" but the output flow will be in the order those inner flows were created. +#### Non-dependent + +In other scenarios, the result(s) of the first source/dataflow is irrelevant and one would like to continue with a quasi independent another source. Here, `flatMap` works as well: + +```java +Observable continued = sourceObservable.flatMapSingle(ignored -> someSingleSource) +continued.map(v -> v.toString()) + .subscribe(System.out::println, Throwable::printStackTrace); +``` + +however, the continuation in this case stays `Observable` instead of the likely more appropriate `Single`. (This is understandable because +from the perspective of `flatMapSingle`, `sourceObservable` is a multi-valued source and thus the mapping may result in multiple values as well). + +Often though there is a way that is somewhat more expressive (and also lower overhead) by using `Completable` as the mediator and its operator `andThen` to resume with something else: + +```java +sourceObservable + .ignoreElements() // returns Completable + .andThen(someSingleSource) + .map(v -> v.toString()) +``` + +The only dependency between the `sourceObservable` and the `someSingleSource` is that the former should complete normally in order for the latter to be consumed. + +#### Deferred-dependent + +Sometimes, there is an implicit data dependency between the previous sequence and the new sequence that, for some reason, was not flowing through the "regular channels". One would be inclined to write such continuations as follows: + +```java +AtomicInteger count = new AtomicInteger(); + +Observable.range(1, 10) + .doOnNext(ignored -> count.incrementAndGet()) + .ignoreElements() + .andThen(Single.just(count.get())) + .subscribe(System.out::println); +``` + +Unfortunately, this prints `0` because `Single.just(count.get())` is evaluated at **assembly time** when the dataflow hasn't even run yet. We need something that defers the evaluation of this `Single` source until **runtime** when the main source completes: + +```java +AtomicInteger count = new AtomicInteger(); + +Observable.range(1, 10) + .doOnNext(ignored -> count.incrementAndGet()) + .ignoreElements() + .andThen(Single.defer(() -> Single.just(count.get()))) + .subscribe(System.out::println); +``` + +or + +```java +AtomicInteger count = new AtomicInteger(); + +Observable.range(1, 10) + .doOnNext(ignored -> count.incrementAndGet()) + .ignoreElements() + .andThen(Single.fromCallable(() -> count.get())) + .subscribe(System.out::println); +``` + + +### Type conversions + +Sometimes, a source or service returns a different type than the flow that is supposed to work with it. For example, in the inventory example above, `getDemandAsync` could return a `Single`. If the code example is left unchanged, this will result in a compile time error (however, often with misleading error message about lack of overload). + +In such situations, there are usually two options to fix the transformation: 1) convert to the desired type or 2) find and use an overload of the specific operator supporting the different type. + +#### Converting to the desired type + +Each reactive base class features operators that can perform such conversions, including the protocol conversions, to match some other type. The following matrix shows the available conversion options: + +| | Flowable | Observable | Single | Maybe | Completable | +|----------|----------|------------|--------|-------|-------------| +|**Flowable** | | `toObservable` | `first`, `firstOrError`, `single`, `singleOrError`, `last`, `lastOrError`1 | `firstElement`, `singleElement`, `lastElement` | `ignoreElements` | +|**Observable**| `toFlowable`2 | | `first`, `firstOrError`, `single`, `singleOrError`, `last`, `lastOrError`1 | `firstElement`, `singleElement`, `lastElement` | `ignoreElements` | +|**Single** | `toFlowable`3 | `toObservable` | | `toMaybe` | `ignoreElement` | +|**Maybe** | `toFlowable`3 | `toObservable` | `toSingle` | | `ignoreElement` | +|**Completable** | `toFlowable` | `toObservable` | `toSingle` | `toMaybe` | | + +1: When turning a multi-valued source into a single valued source, one should decide which of the many source values should be considered as the result. + +2: Turning an `Observable` into `Flowable` requires an additional decision: what to do with the potential unconstrained flow +of the source `Observable`? There are several strategies available (such as buffering, dropping, keeping the latest) via the `BackpressureStrategy` parameter or via standard `Flowable` operators such as `onBackpressureBuffer`, `onBackpressureDrop`, `onBackpressureLatest` which also +allow further customization of the backpressure behavior. + +3: When there is only (at most) one source item, there is no problem with backpressure as it can be always stored until the downstream is ready to consume. + + +#### Using an overload with the desired type + +Many frequently used operator has overloads that can deal with the other types. These are usually named with the suffix of the target type: + +| Operator | Overloads | +|----------|-----------| +| `flatMap` | `flatMapSingle`, `flatMapMaybe`, `flatMapCompletable`, `flatMapIterable` | +| `concatMap` | `concatMapSingle`, `concatMapMaybe`, `concatMapCompletable`, `concatMapIterable` | +| `switchMap` | `switchMapSingle`, `switchMapMaybe`, `switchMapCompletable` | + +The reason these operators have a suffix instead of simply having the same name with different signature is type erasure. Java doesn't consider signatures such as `operator(Function>)` and `operator(Function>)` different (unlike C#) and due to erasure, the two `operator`s would end up as duplicate methods with the same signature. + +### Operator naming conventions + +Naming in programming is one of the hardest things as names are expected to be not long, expressive, capturing and easily memorable. Unfortunately, the target language (and pre-existing conventions) may not give too much help in this regard (unusable keywords, type erasure, type ambiguities, etc.). + +#### Unusable keywords + +In the original Rx.NET, the operator that emits a single item and then completes is called `Return(T)`. Since the Java convention is to have a lowercase letter start a method name, this would have been `return(T)` which is a keyword in Java and thus not available. Therefore, RxJava chose to name this operator `just(T)`. The same limitation exists for the operator `Switch`, which had to be named `switchOnNext`. Yet another example is `Catch` which was named `onErrorResumeNext`. + +#### Type erasure + +Many operators that expect the user to provide some function returning a reactive type can't be overloaded because the type erasure around a `Function` turns such method signatures into duplicates. RxJava chose to name such operators by appending the type as suffix as well: + +```java +Flowable flatMap(Function> mapper) + +Flowable flatMapMaybe(Function> mapper) +``` + +#### Type ambiguities + +Even though certain operators have no problems from type erasure, their signature may turn up being ambiguous, especially if one uses Java 8 and lambdas. For example, there are several overloads of `concatWith` taking the various other reactive base types as arguments (for providing convenience and performance benefits in the underlying implementation): + +```java +Flowable concatWith(Publisher other); + +Flowable concatWith(SingleSource other); +``` + +Both `Publisher` and `SingleSource` appear as functional interfaces (types with one abstract method) and may encourage users to try to provide a lambda expression: + +```java +someSource.concatWith(s -> Single.just(2)) +.subscribe(System.out::println, Throwable::printStackTrace); +``` + +Unfortunately, this approach doesn't work and the example does not print `2` at all. In fact, since version 2.1.10, it doesn't +even compile because at least 4 `concatWith` overloads exist and the compiler finds the code above ambiguous. + +The user in such situations probably wanted to defer some computation until the `someSource` has completed, thus the correct +unambiguous operator should have been `defer`: + +```java +someSource.concatWith(Single.defer(() -> Single.just(2))) +.subscribe(System.out::println, Throwable::printStackTrace); +``` + +Sometimes, a suffix is added to avoid logical ambiguities that may compile but produce the wrong type in a flow: + +```java +Flowable merge(Publisher> sources); + +Flowable mergeArray(Publisher... sources); +``` + +This can get also ambiguous when functional interface types get involved as the type argument `T`. + +#### Error handling + +Dataflows can fail, at which point the error is emitted to the consumer(s). Sometimes though, multiple sources may fail at which point there is a choice whether or not wait for all of them to complete or fail. To indicate this opportunity, many operator names are suffixed with the `DelayError` words (while others feature a `delayError` or `delayErrors` boolean flag in one of their overloads): + +```java +Flowable concat(Publisher> sources); + +Flowable concatDelayError(Publisher> sources); +``` + +Of course, suffixes of various kinds may appear together: + +```java +Flowable concatArrayEagerDelayError(Publisher... sources); +``` + +#### Base class vs base type + +The base classes can be considered heavy due to the sheer number of static and instance methods on them. RxJava 2's design was heavily influenced by the [Reactive Streams](https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams) specification, therefore, the library features a class and an interface per each reactive type: + +| Type | Class | Interface | Consumer | +|------|-------|-----------|----------| +| 0..N backpressured | `Flowable` | `Publisher`1 | `Subscriber` | +| 0..N unbounded | `Observable` | `ObservableSource`2 | `Observer` | +| 1 element or error | `Single` | `SingleSource` | `SingleObserver` | +| 0..1 element or error | `Maybe` | `MaybeSource` | `MaybeObserver` | +| 0 element or error | `Completable` | `CompletableSource` | `CompletableObserver` | + +1The `org.reactivestreams.Publisher` is part of the external Reactive Streams library. It is the main type to interact with other reactive libraries through a standardized mechanism governed by the [Reactive Streams specification](https://github.com/reactive-streams/reactive-streams-jvm#specification). + +2The naming convention of the interface was to append `Source` to the semi-traditional class name. There is no `FlowableSource` since `Publisher` is provided by the Reactive Streams library (and subtyping it wouldn't have helped with interoperation either). These interfaces are, however, not standard in the sense of the Reactive Streams specification and are currently RxJava specific only. + +### Further reading For further details, consult the [wiki](https://github.com/ReactiveX/RxJava/wiki). @@ -205,6 +522,8 @@ All code inside the `io.reactivex.internal.*` packages is considered private API - [Wiki](https://github.com/ReactiveX/RxJava/wiki) - [Javadoc](http://reactivex.io/RxJava/2.x/javadoc/) +- [Latest snaphot Javadoc](http://reactivex.io/RxJava/2.x/javadoc/snapshot/) +- Javadoc of a specific [release version](https://github.com/ReactiveX/RxJava/tags): `http://reactivex.io/RxJava/2.x/javadoc/2.x.y/` ## Binaries @@ -231,7 +550,7 @@ and for Ivy: ``` -Snapshots are available via [JFrog](https://oss.jfrog.org/webapp/#/home): +Snapshots are available via https://oss.jfrog.org/libs-snapshot/io/reactivex/rxjava2/rxjava/ ```groovy repositories { @@ -239,7 +558,7 @@ repositories { } dependencies { - compile 'io.reactivex.rxjava2:rxjava:2.0.0-DP0-SNAPSHOT' + compile 'io.reactivex.rxjava2:rxjava:2.2.0-SNAPSHOT' } ``` @@ -250,7 +569,6 @@ To build: ``` $ git clone git@github.com:ReactiveX/RxJava.git $ cd RxJava/ -$ git checkout -b 2.x $ ./gradlew build ``` @@ -263,19 +581,19 @@ For bugs, questions and discussions please use the [Github Issues](https://githu ## LICENSE -Copyright (c) 2016-present, RxJava Contributors. + Copyright (c) 2016-present, RxJava Contributors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. [beta source link]: https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Beta.java [experimental source link]: https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/annotations/Experimental.java diff --git a/build.gradle b/build.gradle index 854e974cbb..48565d07d0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,78 +1,226 @@ buildscript { - repositories { jcenter() } + repositories { + jcenter() + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } dependencies { - classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.0.0' - classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.1.0' + classpath "ru.vyarus:gradle-animalsniffer-plugin:1.2.0" + classpath "gradle.plugin.nl.javadude.gradle.plugins:license-gradle-plugin:0.13.1" + classpath "me.champeau.gradle:jmh-gradle-plugin:0.4.5" + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3" + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.5.2" } } -group = 'io.reactivex.rxjava2' +group = "io.reactivex.rxjava2" +ext.githubProjectName = "rxjava" -description = 'RxJava: Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.' +version = project.properties["release.version"] -apply plugin: 'java' -// apply plugin: 'pmd' // disabled because runs out of memory on Travis -// apply plugin: 'findbugs' // disabled because runs out of memory on Travis -apply plugin: 'checkstyle' -apply plugin: 'jacoco' -apply plugin: 'ru.vyarus.animalsniffer' -apply plugin: 'nebula.rxjava-project' +def releaseTag = System.getenv("TRAVIS_TAG"); +if (releaseTag != null && !releaseTag.isEmpty()) { + if (releaseTag.startsWith("v")) { + releaseTag = releaseTag.substring(1); + } + version = releaseTag; + project.properties.put("release.version", releaseTag); + + println("Releasing with version " + version); +} + +description = "RxJava: Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM." + +apply plugin: "java-library" +apply plugin: "checkstyle" +apply plugin: "jacoco" +apply plugin: "ru.vyarus.animalsniffer" +apply plugin: "maven" +apply plugin: "osgi" +apply plugin: "me.champeau.gradle.jmh" +apply plugin: "com.github.hierynomus.license" +apply plugin: "com.jfrog.bintray" +apply plugin: "com.jfrog.artifactory" +apply plugin: "eclipse" sourceCompatibility = JavaVersion.VERSION_1_6 targetCompatibility = JavaVersion.VERSION_1_6 -dependencies { - signature 'org.codehaus.mojo.signature:java16:1.1@signature' +// Dependency versions +// --------------------------------------- - compile 'org.reactivestreams:reactive-streams:1.0.0' +def junitVersion = "4.12" +def reactiveStreamsVersion = "1.0.3" +def mockitoVersion = "2.1.0" +def jmhLibVersion = "1.20" +def testNgVersion = "6.11" +def guavaVersion = "24.0-jre" +def jacocoVersion = "0.8.0" +// -------------------------------------- - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.1.0' +repositories { + mavenCentral() +} - perfCompile 'org.openjdk.jmh:jmh-core:1.16' - perfCompile 'org.openjdk.jmh:jmh-generator-annprocess:1.16' +dependencies { + signature "org.codehaus.mojo.signature:java16:1.1@signature" + + api "org.reactivestreams:reactive-streams:$reactiveStreamsVersion" + jmh "org.reactivestreams:reactive-streams:$reactiveStreamsVersion" - testCompile 'org.reactivestreams:reactive-streams-tck:1.0.0' - testCompile group: 'org.testng', name: 'testng', version: '6.9.10' + testImplementation "junit:junit:$junitVersion" + testImplementation "org.mockito:mockito-core:$mockitoVersion" + + testImplementation "org.reactivestreams:reactive-streams-tck:$reactiveStreamsVersion" + testImplementation "org.testng:testng:$testNgVersion" + testImplementation "com.google.guava:guava:$guavaVersion" } javadoc { + failOnError = false exclude "**/internal/**" exclude "**/test/**" exclude "**/perf/**" + exclude "**/jmh/**" options { windowTitle = "RxJava Javadoc ${project.version}" } // Clear the following options to make the docs consistent with the old format - options.addStringOption('top').value = '' - options.addStringOption('doctitle').value = '' - options.addStringOption('header').value = '' - options.links("http://docs.oracle.com/javase/7/docs/api/") + options.addStringOption("top").value = "" + options.addStringOption("doctitle").value = "" + options.addStringOption("header").value = "" + options.stylesheetFile = new File(projectDir, "gradle/stylesheet.css"); + + options.links( + "https://docs.oracle.com/javase/7/docs/api/", + "http://www.reactive-streams.org/reactive-streams-${reactiveStreamsVersion}-javadoc/" + ) + if (JavaVersion.current().isJava7()) { // "./gradle/stylesheet.css" only supports Java 7 - options.addStringOption('stylesheetfile', rootProject.file('./gradle/stylesheet.css').toString()) + options.addStringOption("stylesheetfile", rootProject.file("./gradle/stylesheet.css").toString()) } } animalsniffer { - annotation = 'io.reactivex.internal.util.SuppressAnimalSniffer' + annotation = "io.reactivex.internal.util.SuppressAnimalSniffer" +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = "javadoc" + from javadoc.destinationDir +} + +artifacts { + archives jar + archives sourcesJar + archives javadocJar +} + +jar { + manifest { + name = "rxjava" + instruction "Bundle-Vendor", "RxJava Contributors" + instruction "Bundle-DocURL", "https://github.com/ReactiveX/RxJava" + instruction "Import-Package", "!org.junit,!junit.framework,!org.mockito.*,!org.testng.*,*" + instruction "Eclipse-ExtensibleAPI", "true" + instruction "Automatic-Module-Name", "io.reactivex.rxjava2" + } +} + +license { + header rootProject.file("HEADER") + ext.year = Calendar.getInstance().get(Calendar.YEAR) + skipExistingHeaders true + ignoreFailures true + excludes(["**/*.md", "**/*.txt"]) +} + +apply plugin: "maven-publish" + +install { + repositories.mavenInstaller.pom.project { + name "RxJava" + description "Reactive Extensions for Java" + url "https://github.com/ReactiveX/RxJava" + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + distribution "repo" + } + } + developers { + developer { + id "akarnokd" + name "David Karnok" + email "akarnokd@gmail.com" + } + } + scm { + connection "scm:git:git@github.com:ReactiveX/RxJava.git" + url "scm:git:git@github.com:ReactiveX/RxJava.git" + developerConnection "scm:git:git@github.com:ReactiveX/RxJava.git" + } + issueManagement { + system "github" + url "https://github.com/ReactiveX/RxJava/issues" + } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact (sourcesJar) { + classifier = "sources" + } + } + } +} + +// Reactive-Streams as compile dependency +publishing.publications.all { + pom.withXml { + asNode().dependencies."*".findAll() { + it.scope.text() == "runtime" && project.configurations.compile.allDependencies.find { dep -> + dep.name == it.artifactId.text() + } + }.each { it.scope*.value = "compile"} + } } -// support for snapshot/final releases with the various branches RxJava uses -nebulaRelease { - addReleaseBranchPattern(/\d+\.\d+\.\d+/) - addReleaseBranchPattern('HEAD') +jmh { + jmhVersion = jmhLibVersion + humanOutputFile = null + includeTests = false + jvmArgs = ["-Djmh.ignoreLock=true"] + jvmArgsAppend = ["-Djmh.separateClasspathJAR=true"] + + if (project.hasProperty("jmh")) { + include = ".*" + project.jmh + ".*" + println("JMH: " + include); + } + } -if (project.hasProperty('release.useLastTag')) { - tasks.prepare.enabled = false +plugins.withType(EclipsePlugin) { + project.eclipse.classpath.plusConfigurations += [ configurations.jmh ] } test { testLogging { // showing skipped occasionally should prevent CI timeout due to lack of standard output - events=['skipped', 'failed'] // "started", "passed" + events=["skipped", "failed"] // "started", "passed" // showStandardStreams = true exceptionFormat="full" @@ -88,7 +236,7 @@ test { maxHeapSize = "1200m" - if (System.getenv('CI') == null) { + if (System.getenv("CI") == null) { maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 } } @@ -96,7 +244,7 @@ test { task testng(type: Test) { useTestNG() testLogging { - events=['skipped', 'failed'] + events=["skipped", "failed"] exceptionFormat="full" debug.events = ["skipped", "failed"] @@ -113,10 +261,10 @@ task testng(type: Test) { check.dependsOn testng jacoco { - toolVersion = '0.7.7.201606060606' // See http://www.eclemma.org/jacoco/. + toolVersion = jacocoVersion // See http://www.eclemma.org/jacoco/. } -task GCandMem(dependsOn: 'check') << { +task GCandMem(dependsOn: "check") doLast { print("Memory usage before: ") println(java.lang.management.ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() / 1024.0 / 1024.0) System.gc() @@ -125,7 +273,7 @@ task GCandMem(dependsOn: 'check') << { println(java.lang.management.ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() / 1024.0 / 1024.0) } -task GCandMem2(dependsOn: 'test') << { +task GCandMem2(dependsOn: "test") doLast { print("Memory usage before: ") println(java.lang.management.ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() / 1024.0 / 1024.0) System.gc() @@ -145,7 +293,7 @@ jacocoTestReport { afterEvaluate { classDirectories = files(classDirectories.files.collect { fileTree(dir: it, - exclude: ['io/reactivex/tck/**']) + exclude: ["io/reactivex/tck/**"]) }) } } @@ -154,84 +302,73 @@ jacocoTestReport.dependsOn GCandMem build.dependsOn jacocoTestReport -// pmd { -// toolVersion = '5.4.2' -// ignoreFailures = true -// sourceSets = [sourceSets.main] -// ruleSets = [] -// ruleSetFiles = files('pmd.xml') -// } - -// pmdMain { -// reports { -// html.enabled = true -// xml.enabled = true -// } -// } - -// build.dependsOn pmdMain - -// task pmdPrint(dependsOn: 'pmdMain') << { -// File file = new File('build/reports/pmd/main.xml') -// if (file.exists()) { - -// println("Listing first 100 PMD violations") - -// file.eachLine { line, count -> -// if (count <= 100) { -// println(line) -// } -// } - -// } else { -// println("PMD file not found.") -// } -// } - -// build.dependsOn pmdPrint - checkstyle { - configFile file('checkstyle.xml') + configFile file("checkstyle.xml") ignoreFailures = true toolVersion ="6.19" } -/* -findbugs { - ignoreFailures true - toolVersion = '3.0.1' - effort = 'max' - reportLevel = 'low' - sourceSets = [sourceSets.main] -} - -findbugsMain { - reports { - html.enabled = false // Findbugs can only have on report enabled - xml.enabled = true - } -} -*/ - -// from https://discuss.gradle.org/t/maven-publish-plugin-generated-pom-making-dependency-scope-runtime/7494/10 +if (rootProject.hasProperty("releaseMode")) { -publishing.publications.all { - pom.withXml { - asNode().dependencies.'*'.findAll() { - it.scope.text() == 'runtime' && project.configurations.compile.allDependencies.find { dep -> - dep.name == it.artifactId.text() - } - }.each { it.scope*.value = 'compile'} - - asNode().developers.'*'.findAll() { - it.id.text() == 'benjchristensen' - } .each { - it.id*.value = 'akarnokd' - it.name*.value = 'David Karnok' - it.email*.value = 'akarnokd@gmail.com' + if ("branch".equals(rootProject.releaseMode)) { + // From https://github.com/ReactiveX/RxAndroid/blob/2.x/rxandroid/build.gradle#L94 + + println("ReleaseMode: " + rootProject.releaseMode); + artifactory { + contextUrl = "https://oss.jfrog.org" + + publish { + repository { + repoKey = "oss-snapshot-local" + + username = rootProject.bintrayUser + password = rootProject.bintrayKey + } + + defaults { + publishConfigs("archives") + } + } } - asNode().properties.nebula_Module_Owner*.value = 'akarnokd@gmail.com' - asNode().properties.nebula_Module_Email*.value = 'akarnokd@gmail.com' + build.finalizedBy(artifactoryPublish) } + + if ("full".equals(rootProject.releaseMode)) { + // based on https://github.com/bintray/gradle-bintray-plugin + def rver = version; + + println("ReleaseMode: " + rootProject.releaseMode + " version " + rver); + + bintray { + user = rootProject.bintrayUser + key = rootProject.bintrayKey + configurations = ["archives"] + publish = true + pkg { + repo = "RxJava" + name = "RxJava" + userOrg = "reactivex" + labels = ["rxjava", "reactivex"] + licenses = ["Apache-2.0"] + vcsUrl = "https://github.com/ReactiveX/RxJava.git" + version { + name = rver + gpg { + sign = true + } + mavenCentralSync { + sync = true + user = rootProject.sonatypeUsername + password = rootProject.sonatypePassword + close = "1" + } + } + } + } + + build.finalizedBy(bintrayUpload) + } } + +apply from: file("gradle/javadoc_cleanup.gradle") diff --git a/docs/Additional-Reading.md b/docs/Additional-Reading.md new file mode 100644 index 0000000000..d634e956a6 --- /dev/null +++ b/docs/Additional-Reading.md @@ -0,0 +1,62 @@ +A more complete and up-to-date list of resources can be found at the [reactivex.io site](http://reactivex.io/tutorials.html) + +# Introducing Reactive Programming +* [Introduction to Rx](http://www.introtorx.com/): a free, on-line book by Lee Campbell **(1.x)** +* [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) by Andre Staltz +* [Mastering Observables](http://docs.couchbase.com/developer/java-2.0/observables.html) from the Couchbase documentation **(1.x)** +* [Reactive Programming in Java 8 With RxJava](http://pluralsight.com/training/Courses/TableOfContents/reactive-programming-java-8-rxjava), a course designed by Russell Elledge **(1.x)** +* [33rd Degree Reactive Java](http://www.slideshare.net/tkowalcz/33rd-degree-reactive-java) by Tomasz Kowalczewski **(1.x)** +* [What Every Hipster Should Know About Functional Reactive Programming](http://www.infoq.com/presentations/game-functional-reactive-programming) - Bodil Stokke demos the creation of interactive game mechanics in RxJS +* [Your Mouse is a Database](http://queue.acm.org/detail.cfm?id=2169076) by Erik Meijer +* [A Playful Introduction to Rx](https://www.youtube.com/watch?v=WKore-AkisY) a video lecture by Erik Meijer +* Wikipedia: [Reactive Programming](http://en.wikipedia.org/wiki/Reactive_programming) and [Functional Reactive Programming](http://en.wikipedia.org/wiki/Functional_reactive_programming) +* [What is Reactive Programming?](https://www.youtube.com/watch?v=-8Y1-lE6NSA) a video presentation by Jafar Husain. +* [2 minute introduction to Rx](https://medium.com/@andrestaltz/2-minute-introduction-to-rx-24c8ca793877) by André Staltz +* StackOverflow: [What is (functional) reactive programming?](http://stackoverflow.com/a/1030631/1946802) +* [The Reactive Manifesto](http://www.reactivemanifesto.org/) +* Grokking RxJava, [Part 1: The Basics](http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/), [Part 2: Operator, Operator](http://blog.danlew.net/2014/09/22/grokking-rxjava-part-2/), [Part 3: Reactive with Benefits](http://blog.danlew.net/2014/09/30/grokking-rxjava-part-3/), [Part 4: Reactive Android](http://blog.danlew.net/2014/10/08/grokking-rxjava-part-4/) - published in Sep/Oct 2014 by Daniel Lew **(1.x)** + +# How Netflix Is Using RxJava +* LambdaJam Chicago 2013: [Functional Reactive Programming in the Netflix API](https://speakerdeck.com/benjchristensen/functional-reactive-programming-in-the-netflix-api-lambdajam-2013) by Ben Christensen **(1.x)** +* QCon London 2013 presentation: [Functional Reactive Programming in the Netflix API](http://www.infoq.com/presentations/netflix-functional-rx) and a related [interview](http://www.infoq.com/interviews/christensen-hystrix-rxjava) with Ben Christensen **(1.x)** +* [Functional Reactive in the Netflix API with RxJava](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html) by Ben Christensen and Jafar Husain **(1.x)** +* [Optimizing the Netflix API](http://techblog.netflix.com/2013/01/optimizing-netflix-api.html) by Ben Christensen **(1.x)** +* [Reactive Programming at Netflix](http://techblog.netflix.com/2013/01/reactive-programming-at-netflix.html) by Jafar Husain **(1.x)** + +# RxScala +* [RxJava: Reactive Extensions in Scala](http://www.youtube.com/watch?v=tOMK_FYJREw&feature=youtu.be): video of Ben Christensen and Matt Jacobs presenting at SF Scala **(1.x)** + +# Rx.NET +* [rx.codeplex.com](https://rx.codeplex.com) +* [Rx Design Guidelines (PDF)](http://go.microsoft.com/fwlink/?LinkID=205219) +* [Channel 9 MSDN videos on Reactive Extensions](http://channel9.msdn.com/Tags/reactive+extensions) +* [Beginner’s Guide to the Reactive Extensions](http://msdn.microsoft.com/en-us/data/gg577611) +* [Rx Is now Open Source](http://www.hanselman.com/blog/ReactiveExtensionsRxIsNowOpenSource.aspx) by Scott Hanselman +* [Rx Workshop: Observables vs. Events](http://channel9.msdn.com/Series/Rx-Workshop/Rx-Workshop-Observables-versus-Events) +* [Rx Workshop: Unified Programming Model](http://channel9.msdn.com/Series/Rx-Workshop/Rx-Workshop-Unified-Programming-Model) +* [MSDN Rx forum](http://social.msdn.microsoft.com/Forums/en-US/home?forum=rx) + +# RxJS +* [the RxJS github site](https://github.com/reactivex/rxjs) +* An interactive tutorial: [Functional Programming in Javascript](http://jhusain.github.io/learnrx/) and [an accompanying lecture (video)](http://www.youtube.com/watch?v=LB4lhFJBBq0) by Jafar Husain +* [Netflix JavaScript Talks - Async JavaScript with Reactive Extensions](https://www.youtube.com/watch?v=XRYN2xt11Ek) video of a talk by Jafar Husain about the Rx way of programming +* [RxJS](https://xgrommx.github.io/rx-book/), an on-line book by @xgrommx +* [Journey from procedural to reactive Javascript with stops](https://glebbahmutov.com/blog/journey-from-procedural-to-reactive-javascript-with-stops/) by Gleb Bahmutov + +# RxAndroid + +* [FRP on Android](http://slides.com/yaroslavheriatovych/frponandroid#/) - publish in Jan 2014 by Yaroslav Heriatovych **(1.x)** +* [RxAndroid Github page](https://github.com/ReactiveX/RxAndroid) **(2.x)** +* [RxAndroid basics](https://medium.com/@kurtisnusbaum/rxandroid-basics-part-1-c0d5edcf6850) **(1.x & 2.x)** +* [RxJava and RxAndroid on AndroidHive](https://www.androidhive.info/RxJava/) **(1.x & 2.x)** +* [Reactive Programming with RxAndroid in Kotlin: An Introduction](https://www.raywenderlich.com/384-reactive-programming-with-rxandroid-in-kotlin-an-introduction) **(2.x)** +* [Difference between RxJava and RxAndroid](https://stackoverflow.com/questions/49651249/difference-between-rxjava-and-rxandroid) **(2.x)** +* [Reactive programming with RxAndroid](https://www.androidauthority.com/reactive-programming-with-rxandroid-711104/) **(1.x)** +* [RxJava - Vogella.com](http://www.vogella.com/tutorials/RxJava/article.html) **(2.x)** +* [Funcitional reactive Android](https://www.toptal.com/android/functional-reactive-android-rxjava) **(1.x)** +* [Reactive Programming with RxAndroid and Kotlin](https://www.pluralsight.com/courses/rxandroid-kotlin-reactive-programming) + +# Miscellany +* [RxJava Observables and Akka Actors](http://onoffswitch.net/rxjava-observables-akka-actors/) by Anton Kropp **(1.x & 2.x)** +* [Vert.x and RxJava](http://slid.es/petermd/eclipsecon2014) by @petermd **(1.x)** +* [RxJava in Different Flavours of Java](http://instil.co/2014/08/05/rxjava-in-different-flavours-of-java/): Java 7 and Java 8 implementations of the same code **(1.x)** diff --git a/docs/Alphabetical-List-of-Observable-Operators.md b/docs/Alphabetical-List-of-Observable-Operators.md new file mode 100644 index 0000000000..e5728356bc --- /dev/null +++ b/docs/Alphabetical-List-of-Observable-Operators.md @@ -0,0 +1,250 @@ +* **`aggregate( )`** — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether all items emitted by an Observable meet some criteria +* [**`amb( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — given two or more source Observables, emits all of the items from the first of these Observables to emit an item +* **`ambWith( )`** — _instance version of [**`amb( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`and( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#and-then-and-when) — combine the emissions from two or more source Observables into a `Pattern` (`rxjava-joins`) +* **`apply( )`** (scala) — _see [**`create( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#create)_ +* **`asObservable( )`** (kotlin) — _see [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#from) (et al.)_ +* [**`asyncAction( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert an Action into an Observable that executes the Action and emits its return value (`rxjava-async`) +* [**`asyncFunc( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert a function into an Observable that executes the function and emits its return value (`rxjava-async`) +* [**`averageDouble( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#averagedouble) — calculates the average of Doubles emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageFloat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#averagefloat) — calculates the average of Floats emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageInteger( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — calculates the average of Integers emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — calculates the average of Longs emitted by an Observable and emits this average (`rxjava-math`) +* **`blocking( )`** (clojure) — _see [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time +* [**`byLine( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable of Strings into an Observable of Lines by treating the source sequence as a stream and splitting it on line-endings +* [**`cache( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — remember the sequence of items emitted by the Observable and emit the same sequence to future Subscribers +* [**`cast( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#cast) — cast all items from the source Observable into a particular type before reemitting them +* **`catch( )`** (clojure) — _see [**`onErrorResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorresumenext)_ +* [**`chunkify( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#chunkify) — returns an iterable that periodically returns a list of items emitted by the source Observable since the last list (⁇) +* [**`collect( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#collect) — collects items emitted by the source Observable into a single mutable data structure and returns an Observable that emits this structure +* [**`combineLatest( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#combinelatest) — when an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function +* **`combineLatestWith( )`** (scala) — _instance version of [**`combineLatest( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#combinelatest)_ +* [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html) — concatenate two or more Observables sequentially +* [**`concatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#concatmap) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable, without interleaving +* **`concatWith( )`** — _instance version of [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html)_ +* [**`connect( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — instructs a Connectable Observable to begin emitting items +* **`cons( )`** (clojure) — _see [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html)_ +* [**`contains( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits a particular item or not +* [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count) — counts the number of items emitted by an Observable and emits this count +* [**`countLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count) — counts the number of items emitted by an Observable and emits this count +* [**`create( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#create) — create an Observable from scratch by means of a function +* **`cycle( )`** (clojure) — _see [**`repeat( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables)_ +* [**`debounce( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#debounce) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items +* [**`decode( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — convert a stream of multibyte characters into an Observable that emits byte arrays that respect character boundaries +* [**`defaultIfEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit items from the source Observable, or emit a default item if the source Observable completes after emitting no items +* [**`defer( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#defer) — do not create the Observable until a Subscriber subscribes; create a fresh Observable on each subscription +* [**`deferFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a Future that returns an Observable into an Observable, but do not attempt to get the Observable that the Future returns until a Subscriber subscribes (`rxjava-async`) +* [**`deferCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a Future that returns an Observable into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the returned Observable until a Subscriber subscribes (⁇)(`rxjava-async`) +* [**`delay( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — shift the emissions from an Observable forward in time by a specified amount +* [**`dematerialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — convert a materialized Observable back into its non-materialized form +* [**`distinct( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#distinct) — suppress duplicate items emitted by the source Observable +* [**`distinctUntilChanged( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#distinctuntilchanged) — suppress duplicate consecutive items emitted by the source Observable +* **`do( )`** (clojure) — _see [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* [**`doOnCompleted( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes successfully +* [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take whenever an Observable emits an item +* [**`doOnError( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes with an error +* **`doOnNext( )`** — _see [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* **`doOnRequest( )`** — register an action to take when items are requested from an Observable via reactive-pull backpressure (⁇) +* [**`doOnSubscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an observer subscribes to an Observable +* [**`doOnTerminate( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes, either successfully or with an error +* [**`doOnUnsubscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an observer unsubscribes from an Observable +* [**`doWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) — emit the source Observable's sequence, and then repeat the sequence as long as a condition remains true (`contrib-computation-expressions`) +* **`drop( )`** (scala/clojure) — _see [**`skip( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skip)_ +* **`dropRight( )`** (scala) — _see [**`skipLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skiplast)_ +* **`dropUntil( )`** (scala) — _see [**`skipUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* **`dropWhile( )`** (scala) — _see [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* **`drop-while( )`** (clojure) — _see [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#skipwhile)_ +* [**`elementAt( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#elementat) — emit item _n_ emitted by the source Observable +* [**`elementAtOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit item _n_ emitted by the source Observable, or a default item if the source Observable emits fewer than _n_ items +* [**`empty( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#empty) — create an Observable that emits nothing and then completes +* [**`encode( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — transform an Observable that emits strings into an Observable that emits byte arrays that respect character boundaries of multibyte characters in the original strings +* [**`error( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#error) — create an Observable that emits nothing and then signals an error +* **`every( )`** (clojure) — _see [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* [**`exists( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits any items or not +* [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter) — filter items emitted by an Observable +* **`finally( )`** (clojure) — _see [**`finallyDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* **`filterNot( )`** (scala) — _see [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter)_ +* [**`finallyDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes +* [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#first) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty +* [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty +* **`firstOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable +* [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable) — create Iterables corresponding to each emission from a source Observable and merge the results into a single Observable +* **`flatMapIterableWith( )`** (scala) — _instance version of [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable)_ +* **`flatMapWith( )`** (scala) — _instance version of [**`flatmap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* **`flatten( )`** (scala) — _see [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge)_ +* **`flattenDelayError( )`** (scala) — _see [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror)_ +* **`foldLeft( )`** (scala) — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* **`forall( )`** (scala) — _see [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* **`forEach( )`** (`Observable`) — _see [**`subscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable)_ +* [**`forEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — invoke a function on each item emitted by the Observable; block until the Observable completes +* [**`forEachFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) (`Async`) — pass Subscriber methods to an Observable but also have it behave like a Future that blocks until it completes (`rxjava-async`) +* [**`forEachFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#foreachfuture) (`BlockingObservable`)— create a futureTask that will invoke a specified function on each item emitted by an Observable (⁇) +* [**`forIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#foriterable) — apply a function to the elements of an Iterable to create Observables which are then concatenated (⁇) +* [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#from) — convert an Iterable, a Future, or an Array into an Observable +* [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — convert a stream of characters or a Reader into an Observable that emits byte arrays or Strings +* [**`fromAction( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert an Action into an Observable that invokes the action and emits its result when a Subscriber subscribes (`rxjava-async`) +* [**`fromCallable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a Callable into an Observable that invokes the callable and emits its result or exception when a Subscriber subscribes (`rxjava-async`) +* [**`fromCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a Future into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the Future's value until a Subscriber subscribes (⁇)(`rxjava-async`) +* **`fromFunc0( )`** — _see [**`fromCallable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) (`rxjava-async`)_ +* [**`fromFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromfuture) — convert a Future into an Observable, but do not attempt to get the Future's value until a Subscriber subscribes (⁇) +* [**`fromRunnable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#fromrunnable) — convert a Runnable into an Observable that invokes the runable and emits its result when a Subscriber subscribes (`rxjava-async`) +* [**`generate( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing (⁇) +* [**`generateAbsoluteTime( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing, with each item emitted at an item-specific time (⁇) +* **`generator( )`** (clojure) — _see [**`generate( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime)_ +* [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the sequence emitted by the Observable into an Iterator +* [**`groupBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby) — divide an Observable into a set of Observables that emit groups of items from the original Observable, organized by key +* **`group-by( )`** (clojure) — _see [**`groupBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby)_ +* [**`groupByUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators) — a variant of the [`groupBy( )`](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby) operator that closes any open GroupedObservable upon a signal from another Observable (⁇) +* [**`groupJoin( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#joins) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable +* **`head( )`** (scala) — _see [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* **`headOption( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* **`headOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`ifThen( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — only emit the source Observable's sequence if a condition is true, otherwise emit an empty or default sequence (`contrib-computation-expressions`) +* [**`ignoreElements( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#ignoreelements) — discard the items emitted by the source Observable and only pass through the error or completed notification +* [**`interval( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#interval) — create an Observable that emits a sequence of integers spaced by a given time interval +* **`into( )`** (clojure) — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* [**`isEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits any items or not +* **`items( )`** (scala) — _see [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just)_ +* [**`join( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#joins) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable +* [**`join( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all, separating them by a specified string +* [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just) — convert an object into an Observable that emits that object +* [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable +* [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#last) (`Observable`) — emit only the last item emitted by the source Observable +* **`lastOption( )`** (scala) — _see [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable or a default item if there is no last item +* [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) (`Observable`) — emit only the last item emitted by an Observable, or a default value if the source Observable is empty +* **`lastOrElse( )`** (scala) — _see [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`latest( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that blocks until or unless the Observable emits an item that has not been returned by the iterable, then returns the latest such item +* **`length( )`** (scala) — _see [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count)_ +* **`limit( )`** — _see [**`take( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#take)_ +* **`longCount( )`** (scala) — _see [**`countLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators)_ +* [**`map( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#map) — transform the items emitted by an Observable by applying a function to each of them +* **`mapcat( )`** (clojure) — _see [**`concatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#concatmap)_ +* **`mapMany( )`** — _see: [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* [**`materialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — convert an Observable into a list of Notifications +* [**`max( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#max) — emits the maximum value emitted by a source Observable (`rxjava-math`) +* [**`maxBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — emits the item emitted by the source Observable that has the maximum key value (`rxjava-math`) +* [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge) — combine multiple Observables into one +* [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror) — combine multiple Observables into one, allowing error-free Observables to continue before propagating errors +* **`merge-delay-error( )`** (clojure) — _see [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror)_ +* **`mergeMap( )`** * — _see: [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* **`mergeMapIterable( )`** — _see: [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable)_ +* **`mergeWith( )`** — _instance version of [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge)_ +* [**`min( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#min) — emits the minimum value emitted by a source Observable (`rxjava-math`) +* [**`minBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — emits the item emitted by the source Observable that has the minimum key value (`rxjava-math`) +* [**`mostRecent( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that always returns the item most recently emitted by the Observable +* [**`multicast( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#multicast) — represents an Observable as a Connectable Observable +* [**`never( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#never) — create an Observable that emits nothing at all +* [**`next( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that blocks until the Observable emits another item, then returns that item +* **`nonEmpty( )`** (scala) — _see [**`isEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* **`nth( )`** (clojure) — _see [**`elementAt( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#elementat) and [**`elementAtOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables)_ +* [**`observeOn( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — specify on which Scheduler a Subscriber should observe the Observable +* [**`ofType( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#oftype) — emit only those items from the source Observable that are of a particular class +* [**`onBackpressureBlock( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — block the Observable's thread until the Observer is ready to accept more items from the Observable (⁇) +* [**`onBackpressureBuffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — maintain a buffer of all emissions from the source Observable and emit them to downstream Subscribers according to the requests they generate +* [**`onBackpressureDrop( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — drop emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case emit enough items to fulfill the request +* [**`onErrorFlatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#onerrorflatmap) — instructs an Observable to emit a sequence of items whenever it encounters an error (⁇) +* [**`onErrorResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorresumenext) — instructs an Observable to emit a sequence of items if it encounters an error +* [**`onErrorReturn( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorreturn) — instructs an Observable to emit a particular item when it encounters an error +* [**`onExceptionResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onexceptionresumenext) — instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable) +* **`orElse( )`** (scala) — _see [**`defaultIfEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`parallel( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#parallel) — split the work done on the emissions from an Observable into multiple Observables each operating on its own parallel thread (⁇) +* [**`parallelMerge( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#parallelmerge) — combine multiple Observables into smaller number of Observables (⁇) +* [**`pivot( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#pivot) — combine multiple sets of grouped observables so that they are arranged primarily by group rather than by set (⁇) +* [**`publish( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — represents an Observable as a Connectable Observable +* [**`publishLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#publishlast) — represent an Observable as a Connectable Observable that emits only the last item emitted by the source Observable (⁇) +* [**`range( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#range) — create an Observable that emits a range of sequential integers +* [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce) — apply a function to each emitted item, sequentially, and emit only the final accumulated value +* **`reductions( )`** (clojure) — _see [**`scan( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#scan)_ +* [**`refCount( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — makes a Connectable Observable behave like an ordinary Observable +* [**`repeat( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables) — create an Observable that emits a particular item or sequence of items repeatedly +* [**`repeatWhen( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#repeatwhen) — create an Observable that emits a particular item or sequence of items repeatedly, depending on the emissions of a second Observable +* [**`replay( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators#observablereplay) — ensures that all Subscribers see the same sequence of emitted items, even if they subscribe after the Observable begins emitting the items +* **`rest( )`** (clojure) — _see [**`next( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators#next)_ +* **`return( )`** (clojure) — _see [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just)_ +* [**`retry( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#retry) — if a source Observable emits an error, resubscribe to it in the hopes that it will complete without error +* [**`retrywhen( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#retrywhen) — if a source Observable emits an error, pass that error to another Observable to determine whether to resubscribe to the source +* [**`runAsync( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — returns a `StoppableObservable` that emits multiple actions as generated by a specified Action on a Scheduler (`rxjava-async`) +* [**`sample( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#sample) — emit the most recent items emitted by an Observable within periodic time intervals +* [**`scan( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#scan) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value +* **`seq( )`** (clojure) — _see [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`sequenceEqual( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) — test the equality of sequences emitted by two Observables +* **`sequenceEqualWith( )`** (scala) — _instance version of [**`sequenceEqual( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* [**`serialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators#serialize) — force an Observable to make serialized calls and to be well-behaved +* **`share( )`** — _see [**`refCount( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators)_ +* [**`single( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise throw an exception +* [**`single( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise notify of an exception +* **`singleOption( )`** (scala) — _see [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators#single-and-singleordefault) (`BlockingObservable`)_ +* [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise return a default item +* [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise emit a default item +* **`singleOrElse( )`** (scala) — _see [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* **`size( )`** (scala) — _see [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count)_ +* [**`skip( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skip) — ignore the first _n_ items emitted by an Observable +* [**`skipLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skiplast) — ignore the last _n_ items emitted by an Observable +* [**`skipUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — discard items emitted by a source Observable until a second Observable emits an item, then emit the remainder of the source Observable's items +* [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — discard items emitted by an Observable until a specified condition is false, then emit the remainder +* **`sliding( )`** (scala) — _see [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window)_ +* **`slidingBuffer( )`** (scala) — _see [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer)_ +* [**`split( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable of Strings into an Observable of Strings that treats the source sequence as a stream and splits it on a specified regex boundary +* [**`start( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — create an Observable that emits the return value of a function (`rxjava-async`) +* [**`startCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a function that returns Future into an Observable that emits that Future's return value in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future (⁇)(`rxjava-async`) +* [**`startFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a function that returns Future into an Observable that emits that Future's return value (`rxjava-async`) +* [**`startWith( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#startwith) — emit a specified sequence of items before beginning to emit the items from the Observable +* [**`stringConcat( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all +* [**`subscribeOn( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — specify which Scheduler an Observable should use when its subscription is invoked +* [**`sumDouble( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumdouble) — adds the Doubles emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumFloat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumfloat) — adds the Floats emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumInt( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumint) — adds the Integers emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumlong) — adds the Longs emitted by an Observable and emits this sum (`rxjava-math`) +* **`switch( )`** (scala) — _see [**`switchOnNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#switchonnext)_ +* [**`switchCase( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit the sequence from a particular Observable based on the results of an evaluation (`contrib-computation-expressions`) +* [**`switchMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#switchmap) — transform the items emitted by an Observable into Observables, and mirror those items emitted by the most-recently transformed Observable +* [**`switchOnNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#switchonnext) — convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently emitted of those Observables +* **`synchronize( )`** — _see [**`serialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* [**`take( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#take) — emit only the first _n_ items emitted by an Observable +* [**`takeFirst( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`takeLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#takelast) — only emit the last _n_ items emitted by an Observable +* [**`takeLastBuffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit the last _n_ items emitted by an Observable, as a single list item +* **`takeRight( )`** (scala) — _see [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#last) (`Observable`) or [**`takeLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#takelast)_ +* [**`takeUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emits the items from the source Observable until a second Observable emits an item +* [**`takeWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit items emitted by an Observable as long as a specified condition is true, then skip the remainder +* **`take-while( )`** (clojure) — _see [**`takeWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`then( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#rxjava-joins) — transform a series of `Pattern` objects via a `Plan` template (`rxjava-joins`) +* [**`throttleFirst( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlefirst) — emit the first items emitted by an Observable within periodic time intervals +* [**`throttleLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlelast) — emit the most recent items emitted by an Observable within periodic time intervals +* [**`throttleWithTimeout( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlewithtimeout) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items +* **`throw( )`** (clojure) — _see [**`error( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#error)_ +* [**`timeInterval( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — emit the time lapsed between consecutive emissions of a source Observable +* [**`timeout( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#timeout) — emit items from a source Observable, but issue an exception if no item is emitted in a specified timespan +* [**`timer( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#timer) — create an Observable that emits a single item after a given delay +* [**`timestamp( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — attach a timestamp to every item emitted by an Observable +* [**`toAsync( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a function or Action into an Observable that executes the function and emits its return value (`rxjava-async`) +* [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — transform an Observable into a BlockingObservable +* **`toBlockingObservable( )`** - _see [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`toFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the Observable into a Future +* [**`toIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the sequence emitted by the Observable into an Iterable +* **`toIterator( )`** — _see [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`toList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist) — collect all items from an Observable and emit them as a single List +* [**`toMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tomap) — convert the sequence of items emitted by an Observable into a map keyed by a specified key function +* [**`toMultimap( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tomultimap) — convert the sequence of items emitted by an Observable into an ArrayList that is also a map keyed by a specified key function +* **`toSeq( )`** (scala) — _see [**`toList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist)_ +* [**`toSortedList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tosortedlist) — collect all items from an Observable and emit them as a single, sorted List +* **`tumbling( )`** (scala) — _see [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window)_ +* **`tumblingBuffer( )`** (scala) — _see [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer)_ +* [**`using( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — create a disposable resource that has the same lifespan as an Observable +* [**`when( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#rxjava-joins) — convert a series of `Plan` objects into an Observable (`rxjava-joins`) +* **`where( )`** — _see: [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter)_ +* [**`whileDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — if a condition is true, emit the source Observable's sequence and then repeat the sequence as long as the condition remains true (`contrib-computation-expressions`) +* [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window) — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time +* [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip) — combine sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function +* **`zipWith( )`** — _instance version of [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip)_ +* **`zipWithIndex( )`** (scala) — _see [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip)_ +* **`++`** (scala) — _see [**`concat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators)_ +* **`+:`** (scala) — _see [**`startWith( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#startwith)_ + +(⁇) — this proposed operator is not part of RxJava 1.0 diff --git a/docs/Async-Operators.md b/docs/Async-Operators.md new file mode 100644 index 0000000000..17e3bda561 --- /dev/null +++ b/docs/Async-Operators.md @@ -0,0 +1,11 @@ +The following operators are part of the distinct `rxjava-async` module. They are used to convert synchronous methods into Observables. + +* [**`start( )`**](http://reactivex.io/documentation/operators/start.html) — create an Observable that emits the return value of a function +* [**`toAsync( )` or `asyncAction( )` or `asyncFunc( )`**](http://reactivex.io/documentation/operators/start.html) — convert a function or Action into an Observable that executes the function and emits its return value +* [**`startFuture( )`**](http://reactivex.io/documentation/operators/start.html) — convert a function that returns Future into an Observable that emits that Future's return value +* [**`deferFuture( )`**](http://reactivex.io/documentation/operators/start.html) — convert a Future that returns an Observable into an Observable, but do not attempt to get the Observable that the Future returns until a Subscriber subscribes +* [**`forEachFuture( )`**](http://reactivex.io/documentation/operators/start.html) — pass Subscriber methods to an Observable but also have it behave like a Future that blocks until it completes +* [**`fromAction( )`**](http://reactivex.io/documentation/operators/start.html) — convert an Action into an Observable that invokes the action and emits its result when a Subscriber subscribes +* [**`fromCallable( )`**](http://reactivex.io/documentation/operators/start.html) — convert a Callable into an Observable that invokes the callable and emits its result or exception when a Subscriber subscribes +* [**`fromRunnable( )`**](http://reactivex.io/documentation/operators/start.html) — convert a Runnable into an Observable that invokes the runable and emits its result when a Subscriber subscribes +* [**`runAsync( )`**](http://reactivex.io/documentation/operators/start.html) — returns a `StoppableObservable` that emits multiple actions as generated by a specified Action on a Scheduler \ No newline at end of file diff --git a/docs/Backpressure-(2.0).md b/docs/Backpressure-(2.0).md new file mode 100644 index 0000000000..61361d21c4 --- /dev/null +++ b/docs/Backpressure-(2.0).md @@ -0,0 +1,503 @@ +*Originally contributed to [StackOverflow Documentation](https://stackoverflow.com/documentation/rx-java/2341/backpressure) (going [defunct](https://meta.stackoverflow.com/questions/354217/sunsetting-documentation/)) by [@akarnokd](https://github.com/akarnokd), revised for version 2.x.* + +# Introduction + +**Backpressure** is when in an `Flowable` processing pipeline, some asynchronous stages can't process the values fast enough and need a way to tell the upstream producer to slow down. + +The classic case of the need for backpressure is when the producer is a hot source: + +```java + PublishProcessor source = PublishProcessor.create(); + + source + .observeOn(Schedulers.computation()) + .subscribe(v -> compute(v), Throwable::printStackTrace); + + for (int i = 0; i < 1_000_000; i++) { + source.onNext(i); + } + + Thread.sleep(10_000); +``` + +In this example, the main thread will produce 1 million items to an end consumer which is processing it on a background thread. It is likely the `compute(int)` method takes some time but the overhead of the `Flowable` operator chain may also add to the time it takes to process items. However, the producing thread with the for loop can't know this and keeps `onNext`ing. + +Internally, asynchronous operators have buffers to hold such elements until they can be processed. In the classical Rx.NET and early RxJava, these buffers were unbounded, meaning that they would likely hold nearly all 1 million elements from the example. The problem starts when there are, for example, 1 billion elements or the same 1 million sequence appears 1000 times in a program, leading to `OutOfMemoryError` and generally slowdowns due to excessive GC overhead. + +Similar to how error-handling became a first-class citizen and received operators to deal with it (via `onErrorXXX` operators), backpressure is another property of dataflows that the programmer has to think about and handle (via `onBackpressureXXX` operators). + +Beyond the `PublishProcessor`above, there are other operators that don't support backpressure, mostly due to functional reasons. For example, the operator `interval` emits values periodically, backpressuring it would lead to shifting in the period relative to a wall clock. + +In modern RxJava, most asynchronous operators now have a bounded internal buffer, like `observeOn` above and any attempt to overflow this buffer will terminate the whole sequence with `MissingBackpressureException`. The documentation of each operator has a description about its backpressure behavior. + +However, backpressure is present more subtly in regular cold sequences (which don't and shouldn't yield `MissingBackpressureException`). If the first example is rewritten: + + Flowable.range(1, 1_000_000) + .observeOn(Schedulers.computation()) + .subscribe(v -> compute(v), Throwable::printStackTrace); + + Thread.sleep(10_000); + +There is no error and everything runs smoothly with small memory usage. The reason for this is that many source operators can "generate" values on demand and thus the operator `observeOn` can tell the `range` generate at most so many values the `observeOn` buffer can hold at once without overflow. + +This negotiation is based on the computer science concept of co-routines (I call you, you call me). The operator `range` sends a callback, in the form of an implementation of the `org.reactivestreams.Subscription` interface, to the `observeOn` by calling its (inner `Subscriber`'s) `onSubscribe`. In return, the `observeOn` calls `Subscription.request(n)` with a value to tell the `range` it is allowed to produce (i.e., `onNext` it) that many **additional** elements. It is then the `observeOn`'s responsibility to call the `request` method in the right time and with the right value to keep the data flowing but not overflowing. + +Expressing backpressure in end-consumers is rarely necessary (because they are synchronous in respect to their immediate upstream and backpressure naturally happens due to call-stack blocking), but it may be easier to understand the workings of it: + +```java + Flowable.range(1, 1_000_000) + .subscribe(new DisposableSubscriber() { + @Override + public void onStart() { + request(1); + } + + public void onNext(Integer v) { + compute(v); + + request(1); + } + + @Override + public void onError(Throwable ex) { + ex.printStackTrace(); + } + + @Override + public void onComplete() { + System.out.println("Done!"); + } + }); +``` + +Here the `onStart` implementation indicates `range` to produce its first value, which is then received in `onNext`. Once the `compute(int)` finishes, the another value is then requested from `range`. In a naive implementation of `range`, such call would recursively call `onNext`, leading to `StackOverflowError` which is of course undesirable. + +To prevent this, operators use so-called trampolining logic that prevents such reentrant calls. In `range`'s terms, it will remember that there was a `request(1)` call while it called `onNext()` and once `onNext()` returns, it will make another round and call `onNext()` with the next integer value. Therefore, if the two are swapped, the example still works the same: + +```java + @Override + public void onNext(Integer v) { + request(1); + + compute(v); + } +``` + +However, this is not true for `onStart`. Although the `Flowable` infrastructure guarantees it will be called at most once on each `Subscriber`, the call to `request(1)` may trigger the emission of an element right away. If one has initialization logic after the call to `request(1)` which is needed by `onNext`, you may end up with exceptions: + +```java + Flowable.range(1, 1_000_000) + .subscribe(new DisposableSubscriber() { + + String name; + + @Override + public void onStart() { + request(1); + + name = "RangeExample"; + } + + @Override + public void onNext(Integer v) { + compute(name.length + v); + + request(1); + } + + // ... rest is the same + }); +``` + +In this synchronous case, a `NullPointerException` will be thrown immediately while still executing `onStart`. A more subtle bug happens if the call to `request(1)` triggers an asynchronous call to `onNext` on some other thread and reading `name` in `onNext` races writing it in `onStart` post `request`. + +Therefore, one should do all field initialization in `onStart` or even before that and call `request()` last. Implementations of `request()` in operators ensure proper happens-before relation (or in other terms, memory release or full fence) when necessary. + +# The onBackpressureXXX operators + +Most developers encounter backpressure when their application fails with `MissingBackpressureException` and the exception usually points to the `observeOn` operator. The actual cause is usually the non-backpressured use of `PublishProcessor`, `timer()` or `interval()` or custom operators created via `create()`. + +There are several ways of dealing with such situations. + +## Increasing the buffer sizes + +Sometimes such overflows happen due to bursty sources. Suddenly, the user taps the screen too quickly and `observeOn`'s default 16-element internal buffer on Android overflows. + +Most backpressure-sensitive operators in the recent versions of RxJava now allow programmers to specify the size of their internal buffers. The relevant parameters are usually called `bufferSize`, `prefetch` or `capacityHint`. Given the overflowing example in the introduction, we can just increase the buffer size of `observeOn` to have enough room for all values. + +```java + PublishProcessor source = PublishProcessor.create(); + + source.observeOn(Schedulers.computation(), 1024 * 1024) + .subscribe(e -> { }, Throwable::printStackTrace); + + for (int i = 0; i < 1_000_000; i++) { + source.onNext(i); + } +``` + +Note however that generally, this may be only a temporary fix as the overflow can still happen if the source overproduces the predicted buffer size. In this case, one can use one of the following operators. + +## Batching/skipping values with standard operators + +In case the source data can be processed more efficiently in batch, one can reduce the likelihood of `MissingBackpressureException` by using one of the standard batching operators (by size and/or by time). + +``` + PublishProcessor source = PublishProcessor.create(); + + source + .buffer(1024) + .observeOn(Schedulers.computation(), 1024) + .subscribe(list -> { + list.parallelStream().map(e -> e * e).first(); + }, Throwable::printStackTrace); + + for (int i = 0; i < 1_000_000; i++) { + source.onNext(i); + } +``` + +If some of the values can be safely ignored, one can use the sampling (with time or another `Flowable`) and throttling operators (`throttleFirst`, `throttleLast`, `throttleWithTimeout`). + +```java + PublishProcessor source = PublishProcessor.create(); + + source + .sample(1, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.computation(), 1024) + .subscribe(v -> compute(v), Throwable::printStackTrace); + + for (int i = 0; i < 1_000_000; i++) { + source.onNext(i); + } +``` + +Note hovewer that these operators only reduce the rate of value reception by the downstream and thus they may still lead to `MissingBackpressureException`. + +## onBackpressureBuffer() + +This operator in its parameterless form reintroduces an unbounded buffer between the upstream source and the downstream operator. Being unbounded means as long as the JVM doesn't run out of memory, it can handle almost any amount coming from a bursty source. + +```java + Flowable.range(1, 1_000_000) + .onBackpressureBuffer() + .observeOn(Schedulers.computation(), 8) + .subscribe(e -> { }, Throwable::printStackTrace); +``` + +In this example, the `observeOn` goes with a very low buffer size yet there is no `MissingBackpressureException` as `onBackpressureBuffer` soaks up all the 1 million values and hands over small batches of it to `observeOn`. + +Note however that `onBackpressureBuffer` consumes its source in an unbounded manner, that is, without applying any backpressure to it. This has the consequence that even a backpressure-supporting source such as `range` will be completely realized. + +There are 4 additional overloads of `onBackpressureBuffer` + +### onBackpressureBuffer(int capacity) + +This is a bounded version that signals `BufferOverflowError`in case its buffer reaches the given capacity. + +```java + Flowable.range(1, 1_000_000) + .onBackpressureBuffer(16) + .observeOn(Schedulers.computation()) + .subscribe(e -> { }, Throwable::printStackTrace); +``` + +The relevance of this operator is decreasing as more and more operators now allow setting their buffer sizes. For the rest, this gives an opportunity to "extend their internal buffer" by having a larger number with `onBackpressureBuffer` than their default. + +### onBackpressureBuffer(int capacity, Action onOverflow) + +This overload calls a (shared) action in case an overflow happens. Its usefulness is rather limited as there is no other information provided about the overflow than the current call stack. + +### onBackpressureBuffer(int capacity, Action onOverflow, BackpressureOverflowStrategy strategy) + +This overload is actually more useful as it let's one define what to do in case the capacity has been reached. The `BackpressureOverflow.Strategy` is an interface actually but the class `BackpressureOverflow` offers 4 static fields with implementations of it representing typical actions: + + - `ON_OVERFLOW_ERROR`: this is the default behavior of the previous two overloads, signalling a `BufferOverflowException` + - `ON_OVERFLOW_DEFAULT`: currently it is the same as `ON_OVERFLOW_ERROR` + - `ON_OVERFLOW_DROP_LATEST` : if an overflow would happen, the current value will be simply ignored and only the old values will be delivered once the downstream requests. + - `ON_OVERFLOW_DROP_OLDEST` : drops the oldest element in the buffer and adds the current value to it. + +```java + Flowable.range(1, 1_000_000) + .onBackpressureBuffer(16, () -> { }, + BufferOverflowStrategy.ON_OVERFLOW_DROP_OLDEST) + .observeOn(Schedulers.computation()) + .subscribe(e -> { }, Throwable::printStackTrace); +``` + +Note that the last two strategies cause discontinuity in the stream as they drop out elements. In addition, they won't signal `BufferOverflowException`. + +## onBackpressureDrop() + +Whenever the downstream is not ready to receive values, this operator will drop that elemenet from the sequence. One can think of it as a 0 capacity `onBackpressureBuffer` with strategy `ON_OVERFLOW_DROP_LATEST`. + +This operator is useful when one can safely ignore values from a source (such as mouse moves or current GPS location signals) as there will be more up-to-date values later on. + +```java + component.mouseMoves() + .onBackpressureDrop() + .observeOn(Schedulers.computation(), 1) + .subscribe(event -> compute(event.x, event.y)); +``` + +It may be useful in conjunction with the source operator `interval()`. For example, if one wants to perform some periodic background task but each iteration may last longer than the period, it is safe to drop the excess interval notification as there will be more later on: + +```java + Flowable.interval(1, TimeUnit.MINUTES) + .onBackpressureDrop() + .observeOn(Schedulers.io()) + .doOnNext(e -> networkCall.doStuff()) + .subscribe(v -> { }, Throwable::printStackTrace); +``` + +There exist one overload of this operator: `onBackpressureDrop(Consumer onDrop)` where the (shared) action is called with the value being dropped. This variant allows cleaning up the values themselves (e.g., releasing associated resources). + +## onBackpressureLatest() + +The final operator keeps only the latest value and practically overwrites older, undelivered values. One can think of this as a variant of the `onBackpressureBuffer` with a capacity of 1 and strategy of `ON_OVERFLOW_DROP_OLDEST`. + +Unlike `onBackpressureDrop` there is always a value available for consumption if the downstream happened to be lagging behind. This can be useful in some telemetry-like situations where the data may come in some bursty pattern but only the very latest is interesting for processing. + +For example, if the user clicks a lot on the screen, we'd still want to react to its latest input. + +```java + component.mouseClicks() + .onBackpressureLatest() + .observeOn(Schedulers.computation()) + .subscribe(event -> compute(event.x, event.y), Throwable::printStackTrace); +``` + +The use of `onBackpressureDrop` in this case would lead to a situation where the very last click gets dropped and leaves the user wondering why the business logic wasn't executed. + +# Creating backpressured datasources + +Creating backpressured data sources is the relatively easier task when dealing with backpressure in general because the library already offers static methods on `Flowable` that handle backpressure for the developer. We can distinguish two kinds of factory methods: cold "generators" that either return and generate elements based on downstream demand and hot "pushers" that usually bridge non-reactive and/or non-backpressurable data sources and layer some backpressure handling on top of them. + +## just + +The most basic backpressure aware source is created via `just`: + +```java + Flowable.just(1).subscribe(new DisposableSubscriber() { + @Override + public void onStart() { + request(0); + } + + @Override + public void onNext(Integer v) { + System.out.println(v); + } + + // the rest is omitted for brevity + } +``` + +Since we explicitly don't request in `onStart`, this will not print anything. `just` is great when there is a constant value we'd like to jump-start a sequence. + +Unfortunately, `just` is often mistaken for a way to compute something dynamically to be consumed by `Subscriber`s: + +```java + int counter; + + int computeValue() { + return ++counter; + } + + Flowable o = Flowable.just(computeValue()); + + o.subscribe(System.out:println); + o.subscribe(System.out:println); +``` + +Surprising to some, this prints 1 twice instead of printing 1 and 2 respectively. If the call is rewritten, it becomes obvious why it works so: + +```java + int temp = computeValue(); + + Flowable o = Flowable.just(temp); +``` + +The `computeValue` is called as part of the main routine and not in response to the subscribers subscribing. + +## fromCallable + +What people actually need is the method `fromCallable`: + +```java + Flowable o = Flowable.fromCallable(() -> computeValue()); +``` + +Here the `computeValue` is executed only when a subscriber subscribes and for each of them, printing the expected 1 and 2. Naturally, `fromCallable` also properly supports backpressure and won't emit the computed value unless requested. Note however that the computation does happen anyway. In case the computation itself should be delayed until the downstream actually requests, we can use `just` with `map`: + +```java + Flowable.just("This doesn't matter").map(ignored -> computeValue())... +``` + +`just` won't emit its constant value until requested when it is mapped to the result of the `computeValue`, still called for each subscriber individually. + +## fromArray + +If the data is already available as an array of objects, a list of objects or any `Iterable` source, the respective `from` overloads will handle the backpressure and emission of such sources: + +```java + Flowable.fromArray(1, 2, 3, 4, 5).subscribe(System.out::println); +``` + +For convenience (and avoiding warnings about generic array creation) there are 2 to 10 argument overloads to `just` that internally delegate to `from`. + +The `fromIterable` also gives an interesting opportunity. Many value generation can be expressed in a form of a state-machine. Each requested element triggers a state transition and computation of the returned value. + +Writing such state machines as `Iterable`s is somewhat complicated (but still easier than writing an `Flowable` for consuming it) and unlike C#, Java doesn't have any support from the compiler to build such state machines by simply writing classically looking code (with `yield return` and `yield break`). Some libraries offer some help, such as Google Guava's `AbstractIterable` and IxJava's `Ix.generate()` and `Ix.forloop()`. These are by themselves worthy of a full series so let's see some very basic `Iterable` source that repeats some constant value indefinitely: + +```java + Iterable iterable = () -> new Iterator() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + return 1; + } + }; + + Flowable.fromIterable(iterable).take(5).subscribe(System.out::println); +``` + +If we'd consume the `iterator` via classic for-loop, that would result in an infinite loop. Since we build an `Flowable` out of it, we can express our will to consume only the first 5 of it and then stop requesting anything. This is the true power of lazily evaluating and computing inside `Flowable`s. + +## generate() + +Sometimes, the data source to be converted into the reactive world itself is synchronous (blocking) and pull-like, that is, we have to call some `get` or `read` method to get the next piece of data. One could, of course, turn that into an `Iterable` but when such sources are associated with resources, we may leak those resources if the downstream unsubscribes the sequence before it would end. + +To handle such cases, RxJava has the `generate` factory method family. + +```java + Flowable o = Flowable.generate( + () -> new FileInputStream("data.bin"), + (inputstream, output) -> { + try { + int abyte = inputstream.read(); + if (abyte < 0) { + output.onComplete(); + } else { + output.onNext(abyte); + } + } catch (IOException ex) { + output.onError(ex); + } + return inputstream; + }, + inputstream -> { + try { + inputstream.close(); + } catch (IOException ex) { + RxJavaPlugins.onError(ex); + } + } + ); +``` + +Generally, `generate` uses 3 callbacks. + +The first callbacks allows one to create a per-subscriber state, such as the `FileInputStream` in the example; the file will be opened independently to each individual subscriber. + +The second callback takes this state object and provides an output `Observer` whose `onXXX` methods can be called to emit values. This callback is executed as many times as the downstream requested. At each invocation, it has to call `onNext` at most once optionally followed by either `onError` or `onComplete`. In the example we call `onComplete()` if the read byte is negative, indicating and end of file, and call `onError` in case the read throws an `IOException`. + +The final callback gets invoked when the downstream unsubscribes (closing the inputstream) or when the previous callback called the terminal methods; it allows freeing up resources. Since not all sources need all these features, the static methods of `Flowable.generate` let's one create instances without them. + +Unfortunately, many method calls across the JVM and other libraries throw checked exceptions and need to be wrapped into `try-catch`es as the functional interfaces used by this class don't allow throwing checked exceptions. + +Of course, we can imitate other typical sources, such as an unbounded range with it: + +```java + Flowable.generate( + () -> 0, + (current, output) -> { + output.onNext(current); + return current + 1; + }, + e -> { } + ); +``` + +In this setup, the `current` starts out with `0` and next time the lambda is invoked, the parameter `current` now holds `1`. + +*(Remark: the 1.x classes `SyncOnSubscribe` and `AsyncOnSubscribe` are no longer available.)* + +## create(emitter) + +Sometimes, the source to be wrapped into an `Flowable` is already hot (such as mouse moves) or cold but not backpressurable in its API (such as an asynchronous network callback). + +To handle such cases, a recent version of RxJava introduced the `create(emitter)` factory method. It takes two parameters: + + - a callback that will be called with an instance of the `Emitter` interface for each incoming subscriber, + - a `BackpressureStrategy` enumeration that mandates the developer to specify the backpressure behavior to be applied. It has the usual modes, similar to `onBackpressureXXX` in addition to signalling a `MissingBackpressureException` or simply ignoring such overflow inside it altogether. + +Note that it currently doesn't support additional parameters to those backpressure modes. If one needs those customization, using `NONE` as the backpressure mode and applying the relevant `onBackpressureXXX` on the resulting `Flowable` is the way to go. + +The first typical case for its use when one wants to interact with a push-based source, such as GUI events. Those APIs feature some form of `addListener`/`removeListener` calls that one can utilize: + +```java + Flowable.create(emitter -> { + ActionListener al = e -> { + emitter.onNext(e); + }; + + button.addActionListener(al); + + emitter.setCancellation(() -> + button.removeListener(al)); + + }, BackpressureStrategy.BUFFER); +``` + +The `Emitter` is relatively straightforward to use; one can call `onNext`, `onError` and `onComplete` on it and the operator handles backpressure and unsubscription management on its own. In addition, if the wrapped API supports cancellation (such as the listener removal in the example), one can use the `setCancellation` (or `setSubscription` for `Subscription`-like resources) to register a cancellation callback that gets invoked when the downstream unsubscribes or the `onError`/`onComplete` is called on the provided `Emitter`instance. + +These methods allow only a single resource to be associated with the emitter at a time and setting a new one unsubscribes the old one automatically. If one has to handle multiple resources, create a `CompositeSubscription`, associate it with the emitter and then add further resources to the `CompositeSubscription` itself: + +```java + Flowable.create(emitter -> { + CompositeSubscription cs = new CompositeSubscription(); + + Worker worker = Schedulers.computation().createWorker(); + + ActionListener al = e -> { + emitter.onNext(e); + }; + + button.addActionListener(al); + + cs.add(worker); + cs.add(Subscriptions.create(() -> + button.removeActionListener(al)); + + emitter.setSubscription(cs); + + }, BackpressureMode.BUFFER); +``` + +The second scenario usually involves some asynchronous, callback-based API that has to be converted into an `Flowable`. + +```java + Flowable.create(emitter -> { + + someAPI.remoteCall(new Callback() { + @Override + public void onSuccess(Data data) { + emitter.onNext(data); + emitter.onComplete(); + } + + @Override + public void onFailure(Exception error) { + emitter.onError(error); + } + }); + + }, BackpressureMode.LATEST); +``` + +In this case, the delegation works the same way. Unfortunately, usually, these classical callback-style APIs don't support cancellation, but if they do, one can setup their cancellation just like in the previoius examples (with perhaps a more involved way though). Note the use of the `LATEST` backpressure mode; if we know there will be only a single value, we don't need the `BUFFER` strategy as it allocates a default 128 element long buffer (that grows as necessary) that is never going to be fully utilized. \ No newline at end of file diff --git a/docs/Backpressure.md b/docs/Backpressure.md new file mode 100644 index 0000000000..8feec0d487 --- /dev/null +++ b/docs/Backpressure.md @@ -0,0 +1,175 @@ +# Introduction + +In RxJava it is not difficult to get into a situation in which an Observable is emitting items more rapidly than an operator or subscriber can consume them. This presents the problem of what to do with such a growing backlog of unconsumed items. + +For example, imagine using the [`zip`](http://reactivex.io/documentation/operators/zip.html) operator to zip together two infinite Observables, one of which emits items twice as frequently as the other. A naive implementation of the `zip` operator would have to maintain an ever-expanding buffer of items emitted by the faster Observable to eventually combine with items emitted by the slower one. This could cause RxJava to seize an unwieldy amount of system resources. + +There are a variety of strategies with which you can exercise flow control and backpressure in RxJava in order to alleviate the problems caused when a quickly-producing Observable meets a slow-consuming observer. This page explains some of these strategies, and also shows you how you can design your own Observables and Observable operators to respect requests for flow control. + +## Hot and cold Observables, and multicasted Observables + +A _cold_ Observable emits a particular sequence of items, but can begin emitting this sequence when its Observer finds it to be convenient, and at whatever rate the Observer desires, without disrupting the integrity of the sequence. For example if you convert a static Iterable into an Observable, that Observable will emit the same sequence of items no matter when it is later subscribed to or how frequently those items are observed. Examples of items emitted by a cold Observable might include the results of a database query, file retrieval, or web request. + +A _hot_ Observable begins generating items to emit immediately when it is created. Subscribers typically begin observing the sequence of items emitted by a hot Observable from somewhere in the middle of the sequence, beginning with the first item emitted by the Observable subsequent to the establishment of the subscription. Such an Observable emits items at its own pace, and it is up to its observers to keep up. Examples of items emitted by a hot Observable might include mouse & keyboard events, system events, or stock prices. + +When a cold Observable is _multicast_ (when it is converted into a `ConnectableObservable` and its [`connect()`](http://reactivex.io/documentation/operators/connect.html) method is called), it effectively becomes _hot_ and for the purposes of backpressure and flow-control it should be treated as a hot Observable. + +Cold Observables are ideal for the reactive pull model of backpressure described below. Hot Observables typically do not cope well with a reactive pull model, and are better candidates for some of the other flow control strategies discussed on this page, such as the use of [the `onBackpressureBuffer` or `onBackpressureDrop` operators](http://reactivex.io/documentation/operators/backpressure.html), throttling, buffers, or windows. + +# Useful operators that avoid the need for backpressure + +Your first line of defense against the problems of over-producing Observables is to use some of the ordinary set of Observable operators to reduce the number of emitted items to a more manageable number. The examples in this section will show how you might use such operators to handle a bursty Observable like the one illustrated in the following marble diagram: + +​ + +By fine-tuning the parameters to these operators you can ensure that a slow-consuming observer is not overwhelmed by a fast-producing Observable. + +## Throttling + +Operators like [`sample( )` or `throttleLast( )`](http://reactivex.io/documentation/operators/sample.html), [`throttleFirst( )`](http://reactivex.io/documentation/operators/sample.html), and [`throttleWithTimeout( )` or `debounce( )`](http://reactivex.io/documentation/operators/debounce.html) allow you to regulate the rate at which an Observable emits items. + +The following diagrams show how you could use each of these operators on the bursty Observable shown above. + +### sample (or throttleLast) +The `sample` operator periodically "dips" into the sequence and emits only the most recently emitted item during each dip: + +​ +````groovy +Observable burstySampled = bursty.sample(500, TimeUnit.MILLISECONDS); +```` + +### throttleFirst +The `throttleFirst` operator is similar, but emits not the most recently emitted item, but the first item that was emitted after the previous "dip": + +​ +````groovy +Observable burstyThrottled = bursty.throttleFirst(500, TimeUnit.MILLISECONDS); +```` + +### debounce (or throttleWithTimeout) +The `debounce` operator emits only those items from the source Observable that are not followed by another item within a specified duration: + +​ +````groovy +Observable burstyDebounced = bursty.debounce(10, TimeUnit.MILLISECONDS); +```` + +## Buffers and windows + +You can also use an operator like [`buffer( )`](http://reactivex.io/documentation/operators/buffer.html) or [`window( )`](http://reactivex.io/documentation/operators/window.html) to collect items from the over-producing Observable and then emit them, less-frequently, as collections (or Observables) of items. The slow consumer can then decide whether to process only one particular item from each collection, to process some combination of those items, or to schedule work to be done on each item in the collection, as appropriate. + +The following diagrams show how you could use each of these operators on the bursty Observable shown above. + +### buffer + +You could, for example, close and emit a buffer of items from the bursty Observable periodically, at a regular interval of time: + +​ +````groovy +Observable> burstyBuffered = bursty.buffer(500, TimeUnit.MILLISECONDS); +```` + +Or you could get fancy, and collect items in buffers during the bursty periods and emit them at the end of each burst, by using the `debounce` operator to emit a buffer closing indicator to the `buffer` operator: + +​ +````groovy +// we have to multicast the original bursty Observable so we can use it +// both as our source and as the source for our buffer closing selector: +Observable burstyMulticast = bursty.publish().refCount(); +// burstyDebounced will be our buffer closing selector: +Observable burstyDebounced = burstMulticast.debounce(10, TimeUnit.MILLISECONDS); +// and this, finally, is the Observable of buffers we're interested in: +Observable> burstyBuffered = burstyMulticast.buffer(burstyDebounced); +```` + +### window + +`window` is similar to `buffer`. One variant of `window` allows you to periodically emit Observable windows of items at a regular interval of time: + +​ +````groovy +Observable> burstyWindowed = bursty.window(500, TimeUnit.MILLISECONDS); +```` + +You could also choose to emit a new window each time you have collected a particular number of items from the source Observable: + +​ +````groovy +Observable> burstyWindowed = bursty.window(5); +```` + +# Callstack blocking as a flow-control alternative to backpressure + +Another way of handling an overproductive Observable is to block the callstack (parking the thread that governs the overproductive Observable). This has the disadvantage of going against the “reactive” and non-blocking model of Rx. However this can be a viable option if the problematic Observable is on a thread that can be blocked safely. Currently RxJava does not expose any operators to facilitate this. + +If the Observable, all of the operators that operate on it, and the observer that is subscribed to it, are all operating in the same thread, this effectively establishes a form of backpressure by means of callstack blocking. But be aware that many Observable operators do operate in distinct threads by default (the javadocs for those operators will indicate this). + +# How a subscriber establishes “reactive pull” backpressure + +When you subscribe to an `Observable` with a `Subscriber`, you can request reactive pull backpressure by calling `Subscriber.request(n)` in the `Subscriber`’s `onStart()` method (where _n_ is the maximum number of items you want the `Observable` to emit before the next `request()` call). + +Then, after handling this item (or these items) in `onNext()`, you can call `request()` again to instruct the `Observable` to emit another item (or items). Here is an example of a `Subscriber` that requests one item at a time from `someObservable`: + +````java +someObservable.subscribe(new Subscriber() { + @Override + public void onStart() { + request(1); + } + + @Override + public void onCompleted() { + // gracefully handle sequence-complete + } + + @Override + public void onError(Throwable e) { + // gracefully handle error + } + + @Override + public void onNext(t n) { + // do something with the emitted item "n" + // request another item: + request(1); + } +}); +```` + +You can pass a magic number to `request`, `request(Long.MAX_VALUE)`, to disable reactive pull backpressure and to ask the Observable to emit items at its own pace. `request(0)` is a legal call, but has no effect. Passing values less than zero to `request` will cause an exception to be thrown. + +## Reactive pull backpressure isn’t magic + +Backpressure doesn’t make the problem of an overproducing Observable or an underconsuming Subscriber go away. It just moves the problem up the chain of operators to a point where it can be handled better. + +Let’s take a closer look at the problem of the uneven [`zip`](http://reactivex.io/documentation/operators/zip.html). + +You have two Observables, _A_ and _B_, where _B_ is inclined to emit items more frequently than _A_. When you try to `zip` these two Observables together, the `zip` operator combines item _n_ from _A_ and item _n_ from _B_, but meanwhile _B_ has also emitted items _n_+1 to _n_+_m_. The `zip` operator has to hold on to these items so it can combine them with items _n_+1 to _n_+_m_ from _A_ as they are emitted, but meanwhile _m_ keeps growing and so the size of the buffer needed to hold on to these items keeps increasing. + +You could attach a throttling operator to _B_, but this would mean ignoring some of the items _B_ emits, which might not be appropriate. What you’d really like to do is to signal to _B_ that it needs to slow down and then let _B_ decide how to do this in a way that maintains the integrity of its emissions. + +The reactive pull backpressure model lets you do this. It creates a sort of active pull from the Subscriber in contrast to the normal passive push Observable behavior. + +The `zip` operator as implemented in RxJava uses this technique. It maintains a small buffer of items for each source Observable, and it requests no more items from each source Observable than would fill its buffer. Each time `zip` emits an item, it removes the corresponding items from its buffers and requests exactly one more item from each of its source Observables. + +(Many RxJava operators exercise reactive pull backpressure. Some operators do not need to use this variety of backpressure, as they operate in the same thread as the Observable they operate on, and so they exert a form of blocking backpressure simply by not giving the Observable the opportunity to emit another item until they have finished processing the previous one. For other operators, backpressure is inappropriate as they have been explicitly designed to deal with flow control in other ways. The RxJava javadocs for those operators that are methods of the Observable class indicate which ones do not use reactive pull backpressure and why.) + +For this to work, though, Observables _A_ and _B_ must respond correctly to the `request()`. If an Observable has not been written to support reactive pull backpressure (such support is not a requirement for Observables), you can apply one of the following operators to it, each of which forces a simple form of backpressure behavior: + +
+
onBackpressureBuffer
+
maintains a buffer of all emissions from the source Observable and emits them to downstream Subscribers according to the requests they generate

an experimental version of this operator (not available in RxJava 1.0) allows you to set the capacity of the buffer; applying this operator will cause the resulting Observable to terminate with an error if this buffer is overrun​
+
onBackpressureDrop
+
drops emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case it will emit enough items to fulfill the request
+
onBackpressureBlock (experimental, not in RxJava 1.0)
+
blocks the thread on which the source Observable is operating until such time as a Subscriber issues a request for items, and then unblocks the thread only so long as there are pending requests
+
+ +If you do not apply any of these operators to an Observable that does not support backpressure, _and_ if either you as the Subscriber or some operator between you and the Observable attempts to apply reactive pull backpressure, you will encounter a `MissingBackpressureException` which you will be notified of via your `onError()` callback. + +# Further reading + +If the standard operators are providing the expected behavior, [one can write custom operators in RxJava](https://github.com/ReactiveX/RxJava/wiki/Implementing-custom-operators-(draft)). + +# See also +* [RxJava 0.20.0-RC1 release notes](https://github.com/ReactiveX/RxJava/releases/tag/0.20.0-RC1) diff --git a/docs/Blocking-Observable-Operators.md b/docs/Blocking-Observable-Operators.md new file mode 100644 index 0000000000..64d6e1b40a --- /dev/null +++ b/docs/Blocking-Observable-Operators.md @@ -0,0 +1,49 @@ +This section explains the [`BlockingObservable`](http://reactivex.io/RxJava/javadoc/rx/observables/BlockingObservable.html) subclass. A Blocking Observable extends the ordinary Observable class by providing a set of operators on the items emitted by the Observable that block. + +To transform an `Observable` into a `BlockingObservable`, use the [`Observable.toBlocking( )`](http://reactivex.io/RxJava/javadoc/rx/Observable.html#toBlocking()) method or the [`BlockingObservable.from( )`](http://reactivex.io/RxJava/javadoc/rx/observables/BlockingObservable.html#from(rx.Observable)) method. + +* [**`forEach( )`**](http://reactivex.io/documentation/operators/subscribe.html) — invoke a function on each item emitted by the Observable; block until the Observable completes +* [**`first( )`**](http://reactivex.io/documentation/operators/first.html) — block until the Observable emits an item, then return the first item emitted by the Observable +* [**`firstOrDefault( )`**](http://reactivex.io/documentation/operators/first.html) — block until the Observable emits an item or completes, then return the first item emitted by the Observable or a default item if the Observable did not emit an item +* [**`last( )`**](http://reactivex.io/documentation/operators/last.html) — block until the Observable completes, then return the last item emitted by the Observable +* [**`lastOrDefault( )`**](http://reactivex.io/documentation/operators/last.html) — block until the Observable completes, then return the last item emitted by the Observable or a default item if there is no last item +* [**`mostRecent( )`**](http://reactivex.io/documentation/operators/first.html) — returns an iterable that always returns the item most recently emitted by the Observable +* [**`next( )`**](http://reactivex.io/documentation/operators/takelast.html) — returns an iterable that blocks until the Observable emits another item, then returns that item +* [**`latest( )`**](http://reactivex.io/documentation/operators/first.html) — returns an iterable that blocks until or unless the Observable emits an item that has not been returned by the iterable, then returns that item +* [**`single( )`**](http://reactivex.io/documentation/operators/first.html) — if the Observable completes after emitting a single item, return that item, otherwise throw an exception +* [**`singleOrDefault( )`**](http://reactivex.io/documentation/operators/first.html) — if the Observable completes after emitting a single item, return that item, otherwise return a default item +* [**`toFuture( )`**](http://reactivex.io/documentation/operators/to.html) — convert the Observable into a Future +* [**`toIterable( )`**](http://reactivex.io/documentation/operators/to.html) — convert the sequence emitted by the Observable into an Iterable +* [**`getIterator( )`**](http://reactivex.io/documentation/operators/to.html) — convert the sequence emitted by the Observable into an Iterator + +> This documentation accompanies its explanations with a modified form of "marble diagrams." Here is how these marble diagrams represent Blocking Observables: + + + +#### see also: +* javadoc: `BlockingObservable` +* javadoc: `toBlocking()` +* javadoc: `BlockingObservable.from()` + +## Appendix: similar blocking and non-blocking operators + + + + + + + + + + + + + + + + + + + + +
operatorresult when it acts onequivalent in Rx.NET
Observable that emits multiple itemsObservable that emits one itemObservable that emits no items
Observable.firstthe first itemthe single itemNoSuchElementfirstAsync
BlockingObservable.firstthe first itemthe single itemNoSuchElementfirst
Observable.firstOrDefaultthe first itemthe single itemthe default itemfirstOrDefaultAsync
BlockingObservable.firstOrDefaultthe first itemthe single itemthe default itemfirstOrDefault
Observable.lastthe last itemthe single itemNoSuchElementlastAsync
BlockingObservable.lastthe last itemthe single itemNoSuchElementlast
Observable.lastOrDefaultthe last itemthe single itemthe default itemlastOrDefaultAsync
BlockingObservable.lastOrDefaultthe last itemthe single itemthe default itemlastOrDefault
Observable.singleIllegal Argumentthe single itemNoSuchElementsingleAsync
BlockingObservable.singleIllegal Argumentthe single itemNoSuchElementsingle
Observable.singleOrDefaultIllegal Argumentthe single itemthe default itemsingleOrDefaultAsync
BlockingObservable.singleOrDefaultIllegal Argumentthe single itemthe default itemsingleOrDefault
\ No newline at end of file diff --git a/docs/Combining-Observables.md b/docs/Combining-Observables.md new file mode 100644 index 0000000000..bcc19f6b0f --- /dev/null +++ b/docs/Combining-Observables.md @@ -0,0 +1,166 @@ +This section explains operators you can use to combine multiple Observables. + +# Outline + +- [`combineLatest`](#combineLatest) +- [`join` and `groupJoin`](#joins) +- [`merge`](#merge) +- [`mergeDelayError`](#mergeDelayError) +- [`rxjava-joins`](#rxjava-joins) +- [`startWith`](#startWith) +- [`switchOnNext`](#switchOnNext) +- [`zip`](#zip) + +## startWith + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/startwith.html](http://reactivex.io/documentation/operators/startwith.html) + +Emit a specified sequence of items before beginning to emit the items from the Observable. + +#### startWith Example + +```java +Observable names = Observable.just("Spock", "McCoy"); +names.startWith("Kirk").subscribe(item -> System.out.println(item)); + +// prints Kirk, Spock, McCoy +``` + +## merge + +Combines multiple Observables into one. + + +### merge + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) + +Combines multiple Observables into one. Any `onError` notifications passed from any of the source observables will immediately be passed through to through to the observers and will terminate the merged `Observable`. + +#### merge Example + +```java +Observable.just(1, 2, 3) + .mergeWith(Observable.just(4, 5, 6)) + .subscribe(item -> System.out.println(item)); + +// prints 1, 2, 3, 4, 5, 6 +``` + +### mergeDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/merge.html](http://reactivex.io/documentation/operators/merge.html) + +Combines multiple Observables into one. Any `onError` notifications passed from any of the source observables will be withheld until all merged Observables complete, and only then will be passed along to the observers. + +#### mergeDelayError Example + +```java +Observable observable1 = Observable.error(new IllegalArgumentException("")); +Observable observable2 = Observable.just("Four", "Five", "Six"); +Observable.mergeDelayError(observable1, observable2) + .subscribe(item -> System.out.println(item)); + +// emits 4, 5, 6 and then the IllegalArgumentException (in this specific +// example, this throws an `OnErrorNotImplementedException`). +``` + +## zip + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/zip.html](http://reactivex.io/documentation/operators/zip.html) + +Combines sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function. + +#### zip Example + +```java +Observable firstNames = Observable.just("James", "Jean-Luc", "Benjamin"); +Observable lastNames = Observable.just("Kirk", "Picard", "Sisko"); +firstNames.zipWith(lastNames, (first, last) -> first + " " + last) + .subscribe(item -> System.out.println(item)); + +// prints James Kirk, Jean-Luc Picard, Benjamin Sisko +``` + +## combineLatest + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/combinelatest.html](http://reactivex.io/documentation/operators/combinelatest.html) + +When an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function. + +#### combineLatest Example + +```java +Observable newsRefreshes = Observable.interval(100, TimeUnit.MILLISECONDS); +Observable weatherRefreshes = Observable.interval(50, TimeUnit.MILLISECONDS); +Observable.combineLatest(newsRefreshes, weatherRefreshes, + (newsRefreshTimes, weatherRefreshTimes) -> + "Refreshed news " + newsRefreshTimes + " times and weather " + weatherRefreshTimes) + .subscribe(item -> System.out.println(item)); + +// prints: +// Refreshed news 0 times and weather 0 +// Refreshed news 0 times and weather 1 +// Refreshed news 0 times and weather 2 +// Refreshed news 1 times and weather 2 +// Refreshed news 1 times and weather 3 +// Refreshed news 1 times and weather 4 +// Refreshed news 2 times and weather 4 +// Refreshed news 2 times and weather 5 +// ... +``` + +## switchOnNext + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/switch.html](http://reactivex.io/documentation/operators/switch.html) + +Convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently emitted of those Observables. + +#### switchOnNext Example + +```java +Observable> timeIntervals = + Observable.interval(1, TimeUnit.SECONDS) + .map(ticks -> Observable.interval(100, TimeUnit.MILLISECONDS) + .map(innerInterval -> "outer: " + ticks + " - inner: " + innerInterval)); +Observable.switchOnNext(timeIntervals) + .subscribe(item -> System.out.println(item)); + +// prints: +// outer: 0 - inner: 0 +// outer: 0 - inner: 1 +// outer: 0 - inner: 2 +// outer: 0 - inner: 3 +// outer: 0 - inner: 4 +// outer: 0 - inner: 5 +// outer: 0 - inner: 6 +// outer: 0 - inner: 7 +// outer: 0 - inner: 8 +// outer: 1 - inner: 0 +// outer: 1 - inner: 1 +// outer: 1 - inner: 2 +// outer: 1 - inner: 3 +// ... +``` + +## joins + +* [**`join( )` and `groupJoin( )`**](http://reactivex.io/documentation/operators/join.html) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable + +## rxjava-joins + +* (`rxjava-joins`) [**`and( )`, `then( )`, and `when( )`**](http://reactivex.io/documentation/operators/and-then-when.html) — combine sets of items emitted by two or more Observables by means of `Pattern` and `Plan` intermediaries + +> (`rxjava-joins`) — indicates that this operator is currently part of the optional `rxjava-joins` package under `rxjava-contrib` and is not included with the standard RxJava set of operators diff --git a/docs/Conditional-and-Boolean-Operators.md b/docs/Conditional-and-Boolean-Operators.md new file mode 100644 index 0000000000..f7ed64ce34 --- /dev/null +++ b/docs/Conditional-and-Boolean-Operators.md @@ -0,0 +1,21 @@ +This section explains operators with which you conditionally emit or transform Observables, or can do boolean evaluations of them: + +### Conditional Operators +* [**`amb( )`**](http://reactivex.io/documentation/operators/amb.html) — given two or more source Observables, emits all of the items from the first of these Observables to emit an item +* [**`defaultIfEmpty( )`**](http://reactivex.io/documentation/operators/defaultifempty.html) — emit items from the source Observable, or emit a default item if the source Observable completes after emitting no items +* (`rxjava-computation-expressions`) [**`doWhile( )`**](http://reactivex.io/documentation/operators/repeat.html) — emit the source Observable's sequence, and then repeat the sequence as long as a condition remains true +* (`rxjava-computation-expressions`) [**`ifThen( )`**](http://reactivex.io/documentation/operators/defer.html) — only emit the source Observable's sequence if a condition is true, otherwise emit an empty or default sequence +* [**`skipUntil( )`**](http://reactivex.io/documentation/operators/skipuntil.html) — discard items emitted by a source Observable until a second Observable emits an item, then emit the remainder of the source Observable's items +* [**`skipWhile( )`**](http://reactivex.io/documentation/operators/skipwhile.html) — discard items emitted by an Observable until a specified condition is false, then emit the remainder +* (`rxjava-computation-expressions`) [**`switchCase( )`**](http://reactivex.io/documentation/operators/defer.html) — emit the sequence from a particular Observable based on the results of an evaluation +* [**`takeUntil( )`**](http://reactivex.io/documentation/operators/takeuntil.html) — emits the items from the source Observable until a second Observable emits an item or issues a notification +* [**`takeWhile( )` and `takeWhileWithIndex( )`**](http://reactivex.io/documentation/operators/takewhile.html) — emit items emitted by an Observable as long as a specified condition is true, then skip the remainder +* (`rxjava-computation-expressions`) [**`whileDo( )`**](http://reactivex.io/documentation/operators/repeat.html) — if a condition is true, emit the source Observable's sequence and then repeat the sequence as long as the condition remains true + +> (`rxjava-computation-expressions`) — indicates that this operator is currently part of the optional `rxjava-computation-expressions` package under `rxjava-contrib` and is not included with the standard RxJava set of operators + +### Boolean Operators +* [**`all( )`**](http://reactivex.io/documentation/operators/all.html) — determine whether all items emitted by an Observable meet some criteria +* [**`contains( )`**](http://reactivex.io/documentation/operators/contains.html) — determine whether an Observable emits a particular item or not +* [**`exists( )` and `isEmpty( )`**](http://reactivex.io/documentation/operators/contains.html) — determine whether an Observable emits any items or not +* [**`sequenceEqual( )`**](http://reactivex.io/documentation/operators/sequenceequal.html) — test the equality of the sequences emitted by two Observables diff --git a/docs/Connectable-Observable-Operators.md b/docs/Connectable-Observable-Operators.md new file mode 100644 index 0000000000..a048547529 --- /dev/null +++ b/docs/Connectable-Observable-Operators.md @@ -0,0 +1,75 @@ +This section explains the [`ConnectableObservable`](http://reactivex.io/RxJava/javadoc/rx/observables/ConnectableObservable.html) subclass and its operators: + +* [**`ConnectableObservable.connect( )`**](http://reactivex.io/documentation/operators/connect.html) — instructs a Connectable Observable to begin emitting items +* [**`Observable.publish( )`**](http://reactivex.io/documentation/operators/publish.html) — represents an Observable as a Connectable Observable +* [**`Observable.replay( )`**](http://reactivex.io/documentation/operators/replay.html) — ensures that all Subscribers see the same sequence of emitted items, even if they subscribe after the Observable begins emitting the items +* [**`ConnectableObservable.refCount( )`**](http://reactivex.io/documentation/operators/refcount.html) — makes a Connectable Observable behave like an ordinary Observable + +A Connectable Observable resembles an ordinary Observable, except that it does not begin emitting items when it is subscribed to, but only when its `connect()` method is called. In this way you can wait for all intended Subscribers to subscribe to the Observable before the Observable begins emitting items. + + + +The following example code shows two Subscribers subscribing to the same Observable. In the first case, they subscribe to an ordinary Observable; in the second case, they subscribe to a Connectable Observable that only connects after both Subscribers subscribe. Note the difference in the output: + +**Example #1:** +```groovy +def firstMillion = Observable.range( 1, 1000000 ).sample(7, java.util.concurrent.TimeUnit.MILLISECONDS); + +firstMillion.subscribe( + { println("Subscriber #1:" + it); }, // onNext + { println("Error: " + it.getMessage()); }, // onError + { println("Sequence #1 complete"); } // onCompleted +); + +firstMillion.subscribe( + { println("Subscriber #2:" + it); }, // onNext + { println("Error: " + it.getMessage()); }, // onError + { println("Sequence #2 complete"); } // onCompleted +); +``` +``` +Subscriber #1:211128 +Subscriber #1:411633 +Subscriber #1:629605 +Subscriber #1:841903 +Sequence #1 complete +Subscriber #2:244776 +Subscriber #2:431416 +Subscriber #2:621647 +Subscriber #2:826996 +Sequence #2 complete +``` +**Example #2:** +```groovy +def firstMillion = Observable.range( 1, 1000000 ).sample(7, java.util.concurrent.TimeUnit.MILLISECONDS).publish(); + +firstMillion.subscribe( + { println("Subscriber #1:" + it); }, // onNext + { println("Error: " + it.getMessage()); }, // onError + { println("Sequence #1 complete"); } // onCompleted +); + +firstMillion.subscribe( + { println("Subscriber #2:" + it); }, // onNext + { println("Error: " + it.getMessage()); }, // onError + { println("Sequence #2 complete"); } // onCompleted +); + +firstMillion.connect(); +``` +``` +Subscriber #2:208683 +Subscriber #1:208683 +Subscriber #2:432509 +Subscriber #1:432509 +Subscriber #2:644270 +Subscriber #1:644270 +Subscriber #2:887885 +Subscriber #1:887885 +Sequence #2 complete +Sequence #1 complete +``` + +#### see also: +* javadoc: `ConnectableObservable` +* Introduction to Rx: Publish and Connect \ No newline at end of file diff --git a/docs/Creating-Observables.md b/docs/Creating-Observables.md new file mode 100644 index 0000000000..360e03ab09 --- /dev/null +++ b/docs/Creating-Observables.md @@ -0,0 +1,426 @@ +This page shows methods that create reactive sources, such as `Observable`s. + +### Outline + +- [`create`](#create) +- [`defer`](#defer) +- [`empty`](#empty) +- [`error`](#error) +- [`from`](#from) +- [`generate`](#generate) +- [`interval`](#interval) +- [`just`](#just) +- [`never`](#never) +- [`range`](#range) +- [`timer`](#timer) + +## just + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/just.html](http://reactivex.io/documentation/operators/just.html) + +Constructs a reactive type by taking a pre-existing object and emitting that specific object to the downstream consumer upon subscription. + +#### just example: + +```java +String greeting = "Hello world!"; + +Observable observable = Observable.just(greeting); + +observable.subscribe(item -> System.out.println(item)); +``` + +There exist overloads with 2 to 9 arguments for convenience, which objects (with the same common type) will be emitted in the order they are specified. + +```java +Observable observable = Observable.just("1", "A", "3.2", "def"); + + observable.subscribe(item -> System.out.print(item), error -> error.printStackTrace(), + () -> System.out.println()); +``` + +## From + +Constructs a sequence from a pre-existing source or generator type. + +*Note: These static methods use the postfix naming convention (i.e., the argument type is repeated in the method name) to avoid overload resolution ambiguities.* + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/from.html](http://reactivex.io/documentation/operators/from.html) + +### fromIterable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +Signals the items from a `java.lang.Iterable` source (such as `List`s, `Set`s or `Collection`s or custom `Iterable`s) and then completes the sequence. + +#### fromIterable example: + +```java +List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); + +Observable observable = Observable.fromIterable(list); + +observable.subscribe(item -> System.out.println(item), error -> error.printStackTrace(), + () -> System.out.println("Done")); +``` + +### fromArray + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +Signals the elements of the given array and then completes the sequence. + +#### fromArray example: + +```java +Integer[] array = new Integer[10]; +for (int i = 0; i < array.length; i++) { + array[i] = i; +} + +Observable observable = Observable.fromArray(array); + +observable.subscribe(item -> System.out.println(item), error -> error.printStackTrace(), + () -> System.out.println("Done")); +``` + +*Note: RxJava does not support primitive arrays, only (generic) reference arrays.* + +### fromCallable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +When a consumer subscribes, the given `java.util.concurrent.Callable` is invoked and its returned value (or thrown exception) is relayed to that consumer. + +#### fromCallable example: + +```java +Callable callable = () -> { + System.out.println("Hello World!"); + return "Hello World!"); +} + +Observable observable = Observable.fromCallable(callable); + +observable.subscribe(item -> System.out.println(item), error -> error.printStackTrace(), + () -> System.out.println("Done")); +``` + +*Remark: In `Completable`, the actual returned value is ignored and the `Completable` simply completes.* + +## fromAction + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +When a consumer subscribes, the given `io.reactivex.function.Action` is invoked and the consumer completes or receives the exception the `Action` threw. + +#### fromAction example: + +```java +Action action = () -> System.out.println("Hello World!"); + +Completable completable = Completable.fromAction(action); + +completable.subscribe(() -> System.out.println("Done"), error -> error.printStackTrace()); +``` + +*Note: the difference between `fromAction` and `fromRunnable` is that the `Action` interface allows throwing a checked exception while the `java.lang.Runnable` does not.* + +## fromRunnable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +When a consumer subscribes, the given `io.reactivex.function.Action` is invoked and the consumer completes or receives the exception the `Action` threw. + +#### fromRunnable example: + +```java +Runnable runnable = () -> System.out.println("Hello World!"); + +Completable completable = Completable.fromRunnable(runnable); + +completable.subscribe(() -> System.out.println("Done"), error -> error.printStackTrace()); +``` + +*Note: the difference between `fromAction` and `fromRunnable` is that the `Action` interface allows throwing a checked exception while the `java.lang.Runnable` does not.* + +### fromFuture + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +Given a pre-existing, already running or already completed `java.util.concurrent.Future`, wait for the `Future` to complete normally or with an exception in a blocking fashion and relay the produced value or exception to the consumers. + +#### fromFuture example: + +```java +ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + +Future future = executor.schedule(() -> "Hello world!", 1, TimeUnit.SECONDS); + +Observable observable = Observable.fromFuture(future); + +observable.subscribe( + item -> System.out.println(item), + error -> error.printStackTrace(), + () -> System.out.println("Done")); + +executor.shutdown(); +``` + +### from{reactive type} + +Wraps or converts another reactive type to the target reactive type. + +The following combinations are available in the various reactive types with the following signature pattern: `targetType.from{sourceType}()` + +**Available in:** + +targetType \ sourceType | Publisher | Observable | Maybe | Single | Completable +----|---------------|-----------|---------|-----------|---------------- +Flowable | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | | | | | +Observable | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | | | | | +Maybe | | | | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) +Single | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | | | +Completable | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) | + +*Note: not all possible conversion is implemented via the `from{reactive type}` method families. Check out the `to{reactive type}` method families for further conversion possibilities. + +#### from{reactive type} example: + +```java +Flux reactorFlux = Flux.fromCompletionStage(CompletableFuture.completedFuture(1)); + +Observable observable = Observable.fromPublisher(reactorFlux); + +observable.subscribe( + item -> System.out.println(item), + error -> error.printStackTrace(), + () -> System.out.println("Done")); +``` + +## generate + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/create.html](http://reactivex.io/documentation/operators/create.html) + +Creates a cold, synchronous and stateful generator of values. + +#### generate example: + +```java +int startValue = 1; +int incrementValue = 1; +Flowable flowable = Flowable.generate(() -> startValue, (s, emitter) -> { + int nextValue = s + incrementValue; + emitter.onNext(nextValue); + return nextValue; +}); +flowable.subscribe(value -> System.out.println(value)); +``` + +## create + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/create.html](http://reactivex.io/documentation/operators/create.html) + +Construct a **safe** reactive type instance which when subscribed to by a consumer, runs an user-provided function and provides a type-specific `Emitter` for this function to generate the signal(s) the designated business logic requires. This method allows bridging the non-reactive, usually listener/callback-style world, with the reactive world. + +#### create example: + +```java +ScheduledExecutorService executor = Executors.newSingleThreadedScheduledExecutor(); + +ObservableOnSubscribe handler = emitter -> { + + Future future = executor.schedule(() -> { + emitter.onNext("Hello"); + emitter.onNext("World"); + emitter.onComplete(); + return null; + }, 1, TimeUnit.SECONDS); + + emitter.setCancellable(() -> future.cancel(false)); +}; + +Observable observable = Observable.create(handler); + +observable.subscribe(item -> System.out.println(item), error -> error.printStackTrace(), + () -> System.out.println("Done")); + +Thread.sleep(2000); +executor.shutdown(); +``` + +*Note: `Flowable.create()` must also specify the backpressure behavior to be applied when the user-provided function generates more items than the downstream consumer has requested.* + +## defer + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/defer.html](http://reactivex.io/documentation/operators/defer.html) + +Calls an user-provided `java.util.concurrent.Callable` when a consumer subscribes to the reactive type so that the `Callable` can generate the actual reactive instance to relay signals from towards the consumer. `defer` allows: + +- associating a per-consumer state with such generated reactive instances, +- allows executing side-effects before an actual/generated reactive instance gets subscribed to, +- turn hot sources (i.e., `Subject`s and `Processor`s) into cold sources by basically making those hot sources not exist until a consumer subscribes. + +#### defer example: + +```java +Observable observable = Observable.defer(() -> { + long time = System.currentTimeMillis(); + return Observable.just(time); +}); + +observable.subscribe(time -> System.out.println(time)); + +Thread.sleep(1000); + +observable.subscribe(time -> System.out.println(time)); +``` + +## range + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/range.html](http://reactivex.io/documentation/operators/range.html) + +Generates a sequence of values to each individual consumer. The `range()` method generates `Integer`s, the `rangeLong()` generates `Long`s. + +#### range example: +```java +String greeting = "Hello World!"; + +Observable indexes = Observable.range(0, greeting.length()); + +Observable characters = indexes + .map(index -> greeting.charAt(index)); + +characters.subscribe(character -> System.out.print(character), error -> error.printStackTrace(), + () -> System.out.println()); +``` + +## interval + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/interval.html](http://reactivex.io/documentation/operators/interval.html) + +Periodically generates an infinite, ever increasing numbers (of type `Long`). The `intervalRange` variant generates a limited amount of such numbers. + +#### interval example: + +```java +Observable clock = Observable.interval(1, TimeUnit.SECONDS); + +clock.subscribe(time -> { + if (time % 2 == 0) { + System.out.println("Tick"); + } else { + System.out.println("Tock"); + } +}); +``` + +## timer + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/timer.html](http://reactivex.io/documentation/operators/timer.html) + +After the specified time, this reactive source signals a single `0L` (then completes for `Flowable` and `Observable`). + +#### timer example: + +```java +Observable eggTimer = Observable.timer(5, TimeUnit.MINUTES); + +eggTimer.blockingSubscribe(v -> System.out.println("Egg is ready!")); +``` + +## empty + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) + +This type of source signals completion immediately upon subscription. + +#### empty example: + +```java +Observable empty = Observable.empty(); + +empty.subscribe( + v -> System.out.println("This should never be printed!"), + error -> System.out.println("Or this!"), + () -> System.out.println("Done will be printed.")); +``` + +## never + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) + +This type of source does not signal any `onNext`, `onSuccess`, `onError` or `onComplete`. This type of reactive source is useful in testing or "disabling" certain sources in combinator operators. + +#### never example: + +```java +Observable never = Observable.never(); + +never.subscribe( + v -> System.out.println("This should never be printed!"), + error -> System.out.println("Or this!"), + () -> System.out.println("This neither!")); +``` + +## error + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/empty-never-throw.html](http://reactivex.io/documentation/operators/empty-never-throw.html) + +Signal an error, either pre-existing or generated via a `java.util.concurrent.Callable`, to the consumer. + +#### error example: + +```java +Observable error = Observable.error(new IOException()); + +error.subscribe( + v -> System.out.println("This should never be printed!"), + e -> e.printStackTrace(), + () -> System.out.println("This neither!")); +``` + +A typical use case is to conditionally map or suppress an exception in a chain utilizing `onErrorResumeNext`: + +```java +Observable observable = Observable.fromCallable(() -> { + if (Math.random() < 0.5) { + throw new IOException(); + } + throw new IllegalArgumentException(); +}); + +Observable result = observable.onErrorResumeNext(error -> { + if (error instanceof IllegalArgumentException) { + return Observable.empty(); + } + return Observable.error(error); +}); + +for (int i = 0; i < 10; i++) { + result.subscribe( + v -> System.out.println("This should never be printed!"), + error -> error.printStackTrace(), + () -> System.out.println("Done")); +} +``` \ No newline at end of file diff --git a/docs/Error-Handling-Operators.md b/docs/Error-Handling-Operators.md new file mode 100644 index 0000000000..0b2eace61f --- /dev/null +++ b/docs/Error-Handling-Operators.md @@ -0,0 +1,284 @@ +There are a variety of operators that you can use to react to or recover from `onError` notifications from reactive sources, such as `Observable`s. For example, you might: + +1. swallow the error and switch over to a backup Observable to continue the sequence +1. swallow the error and emit a default item +1. swallow the error and immediately try to restart the failed Observable +1. swallow the error and try to restart the failed Observable after some back-off interval + +# Outline + +- [`doOnError`](#doonerror) +- [`onErrorComplete`](#onerrorcomplete) +- [`onErrorResumeNext`](#onerrorresumenext) +- [`onErrorReturn`](#onerrorreturn) +- [`onErrorReturnItem`](#onerrorreturnitem) +- [`onExceptionResumeNext`](#onexceptionresumenext) +- [`retry`](#retry) +- [`retryUntil`](#retryuntil) +- [`retryWhen`](#retrywhen) + +## doOnError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/do.html](http://reactivex.io/documentation/operators/do.html) + +Instructs a reactive type to invoke the given `io.reactivex.functions.Consumer` when it encounters an error. + +### doOnError example + +```java +Observable.error(new IOException("Something went wrong")) + .doOnError(error -> System.err.println("The error message is: " + error.getMessage())) + .subscribe( + x -> System.out.println("onNext should never be printed!"), + Throwable::printStackTrace, + () -> System.out.println("onComplete should never be printed!")); +``` + +## onErrorComplete + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to swallow an error event and replace it by a completion event. + +Optionally, a `io.reactivex.functions.Predicate` can be specified that gives more control over when an error event should be replaced by a completion event, and when not. + +### onErrorComplete example + +```java +Completable.fromAction(() -> { + throw new IOException(); +}).onErrorComplete(error -> { + // Only ignore errors of type java.io.IOException. + return error instanceof IOException; +}).subscribe( + () -> System.out.println("IOException was ignored"), + error -> System.err.println("onError should not be printed!")); +``` + +## onErrorResumeNext + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit a sequence of items if it encounters an error. + +### onErrorResumeNext example + +```java + Observable numbers = Observable.generate(() -> 1, (state, emitter) -> { + emitter.onNext(state); + + return state + 1; +}); + +numbers.scan(Math::multiplyExact) + .onErrorResumeNext(Observable.empty()) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints: +// 1 +// 2 +// 6 +// 24 +// 120 +// 720 +// 5040 +// 40320 +// 362880 +// 3628800 +// 39916800 +// 479001600 +``` + +## onErrorReturn + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit the item returned by the specified `io.reactivex.functions.Function` when it encounters an error. + +### onErrorReturn example + +```java +Single.just("2A") + .map(v -> Integer.parseInt(v, 10)) + .onErrorReturn(error -> { + if (error instanceof NumberFormatException) return 0; + else throw new IllegalArgumentException(); + }) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints 0 +``` + +## onErrorReturnItem + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to emit a particular item when it encounters an error. + +### onErrorReturnItem example + +```java +Single.just("2A") + .map(v -> Integer.parseInt(v, 10)) + .onErrorReturnItem(0) + .subscribe( + System.out::println, + error -> System.err.println("onError should not be printed!")); + +// prints 0 +``` + +## onExceptionResumeNext + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/catch.html](http://reactivex.io/documentation/operators/catch.html) + +Instructs a reactive type to continue emitting items after it encounters an `java.lang.Exception`. Unlike [`onErrorResumeNext`](#onerrorresumenext), this one lets other types of `Throwable` continue through. + +### onExceptionResumeNext example + +```java +Observable exception = Observable.error(IOException::new) + .onExceptionResumeNext(Observable.just("This value will be used to recover from the IOException")); + +Observable error = Observable.error(Error::new) + .onExceptionResumeNext(Observable.just("This value will not be used")); + +Observable.concat(exception, error) + .subscribe( + message -> System.out.println("onNext: " + message), + err -> System.err.println("onError: " + err)); + +// prints: +// onNext: This value will be used to recover from the IOException +// onError: java.lang.Error +``` + +## retry + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to resubscribe to the source reactive type if it encounters an error in the hopes that it will complete without error. + +### retry example + +```java +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }); + +source.retry((retryCount, error) -> retryCount < 3) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.err.println("onError: " + error.getMessage())); + +// prints: +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onError: Something went wrong! +``` + +## retryUntil + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to resubscribe to the source reactive type if it encounters an error until the given `io.reactivex.functions.BooleanSupplier` returns `true`. + +### retryUntil example + +```java +LongAdder errorCounter = new LongAdder(); +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }) + .doOnError((error) -> errorCounter.increment()); + +source.retryUntil(() -> errorCounter.intValue() >= 3) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.err.println("onError: " + error.getMessage())); + +// prints: +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onNext: 0 +// onNext: 1 +// onError: Something went wrong! +``` + +## retryWhen + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/retry.html](http://reactivex.io/documentation/operators/retry.html) + +Instructs a reactive type to pass any error to another `Observable` or `Flowable` to determine whether to resubscribe to the source. + +### retryWhen example + +```java +Observable source = Observable.interval(0, 1, TimeUnit.SECONDS) + .flatMap(x -> { + if (x >= 2) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x); + }); + +source.retryWhen(errors -> { + return errors.map(error -> 1) + + // Count the number of errors. + .scan(Math::addExact) + + .doOnNext(errorCount -> System.out.println("No. of errors: " + errorCount)) + + // Limit the maximum number of retries. + .takeWhile(errorCount -> errorCount < 3) + + // Signal resubscribe event after some delay. + .flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS)); +}).blockingSubscribe( + x -> System.out.println("onNext: " + x), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: 0 +// onNext: 1 +// No. of errors: 1 +// onNext: 0 +// onNext: 1 +// No. of errors: 2 +// onNext: 0 +// onNext: 1 +// No. of errors: 3 +// onComplete +``` diff --git a/docs/Error-Handling.md b/docs/Error-Handling.md new file mode 100644 index 0000000000..3de9c396d9 --- /dev/null +++ b/docs/Error-Handling.md @@ -0,0 +1,24 @@ +An Observable typically does not _throw_ exceptions. Instead it notifies any observers that an unrecoverable error has occurred by terminating the Observable sequence with an `onError` notification. + +There are some exceptions to this. For example, if the `onError()` call _itself_ fails, the Observable will not attempt to notify the observer of this by again calling `onError` but will throw a `RuntimeException`, an `OnErrorFailedException`, or an `OnErrorNotImplementedException`. + +# Techniques for recovering from onError notifications + +So rather than _catch_ exceptions, your observer or operator should more typically respond to `onError` notifications of exceptions. There are also a variety of Observable operators that you can use to react to or recover from `onError` notifications from Observables. For example, you might use an operator to: + +1. swallow the error and switch over to a backup Observable to continue the sequence +1. swallow the error and emit a default item +1. swallow the error and immediately try to restart the failed Observable +1. swallow the error and try to restart the failed Observable after some back-off interval + +You can use the operators described in [[Error Handling Operators]] to implement these strategies. + +# RxJava-specific exceptions and what to do about them + +
+
CompositeException
This indicates that more than one exception occurred. You can use the exception’s getExceptions() method to retrieve the individual exceptions that make up the composite.
+
MissingBackpressureException
This indicates that a Subscriber or operator attempted to apply reactive pull backpressure to an Observable that does not implement it. See [[Backpressure]] for work-arounds for Observables that do not implement reactive pull backpressure.
+
OnErrorFailedException
This indicates that an Observable tried to call its observer’s onError() method, but that method itself threw an exception.
+
OnErrorNotImplementedException
This indicates that an Observable tried to call its observer’s onError() method, but that no such method existed. You can eliminate this by either fixing the Observable so that it no longer reaches an error condition, by implementing an onError handler in the observer, or by intercepting the onError notification before it reaches the observer by using one of the operators described elsewhere on this page.
+
OnErrorThrowable
Observers pass throwables of this sort into their observers’ onError() handlers. A Throwable of this variety contains more information about the error and about the Observable-specific state of the system at the time of the error than does a standard Throwable.
+
\ No newline at end of file diff --git a/docs/Filtering-Observables.md b/docs/Filtering-Observables.md new file mode 100644 index 0000000000..620800dc8c --- /dev/null +++ b/docs/Filtering-Observables.md @@ -0,0 +1,760 @@ +This page shows operators you can use to filter and select items emitted by reactive sources, such as `Observable`s. + +# Outline + +- [`debounce`](#debounce) +- [`distinct`](#distinct) +- [`distinctUntilChanged`](#distinctuntilchanged) +- [`elementAt`](#elementat) +- [`elementAtOrError`](#elementatorerror) +- [`filter`](#filter) +- [`first`](#first) +- [`firstElement`](#firstelement) +- [`firstOrError`](#firstorerror) +- [`ignoreElement`](#ignoreelement) +- [`ignoreElements`](#ignoreelements) +- [`last`](#last) +- [`lastElement`](#lastelement) +- [`lastOrError`](#lastorerror) +- [`ofType`](#oftype) +- [`sample`](#sample) +- [`skip`](#skip) +- [`skipLast`](#skiplast) +- [`take`](#take) +- [`takeLast`](#takelast) +- [`throttleFirst`](#throttlefirst) +- [`throttleLast`](#throttlelast) +- [`throttleLatest`](#throttlelatest) +- [`throttleWithTimeout`](#throttlewithtimeout) +- [`timeout`](#timeout) + +## debounce + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/debounce.html](http://reactivex.io/documentation/operators/debounce.html) + +Drops items emitted by a reactive source that are followed by newer items before the given timeout value expires. The timer resets on each emission. + +This operator keeps track of the most recent emitted item, and emits this item only when enough time has passed without the source emitting any other items. + +### debounce example + +```java +// Diagram: +// -A--------------B----C-D-------------------E-|----> +// a---------1s +// b---------1s +// c---------1s +// d---------1s +// e-|----> +// -----------A---------------------D-----------E-|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(1_500); + emitter.onNext("B"); + + Thread.sleep(500); + emitter.onNext("C"); + + Thread.sleep(250); + emitter.onNext("D"); + + Thread.sleep(2_000); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .debounce(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onNext: E +// onComplete +``` + +## distinct + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/distinct.html](http://reactivex.io/documentation/operators/distinct.html) + +Filters a reactive source by only emitting items that are distinct by comparison from previous items. A `io.reactivex.functions.Function` can be specified that projects each item emitted by the source into a new value that will be used for comparison with previous projected values. + +### distinct example + +```java +Observable.just(2, 3, 4, 4, 2, 1) + .distinct() + .subscribe(System.out::println); + +// prints: +// 2 +// 3 +// 4 +// 1 +``` + +## distinctUntilChanged + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/distinct.html](http://reactivex.io/documentation/operators/distinct.html) + +Filters a reactive source by only emitting items that are distinct by comparison from their immediate predecessors. A `io.reactivex.functions.Function` can be specified that projects each item emitted by the source into a new value that will be used for comparison with previous projected values. Alternatively, a `io.reactivex.functions.BiPredicate` can be specified that is used as the comparator function to compare immediate predecessors with each other. + +### distinctUntilChanged example + +```java +Observable.just(1, 1, 2, 1, 2, 3, 3, 4) + .distinctUntilChanged() + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 1 +// 2 +// 3 +// 4 +``` + +## elementAt + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/elementat.html](http://reactivex.io/documentation/operators/elementat.html) + +Emits the single item at the specified zero-based index in a sequence of emissions from a reactive source. A default item can be specified that will be emitted if the specified index is not within the sequence. + +### elementAt example + +```java +Observable source = Observable.generate(() -> 1L, (state, emitter) -> { + emitter.onNext(state); + + return state + 1L; +}).scan((product, x) -> product * x); + +Maybe element = source.elementAt(5); +element.subscribe(System.out::println); + +// prints 720 +``` + +## elementAtOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/elementat.html](http://reactivex.io/documentation/operators/elementat.html) + +Emits the single item at the specified zero-based index in a sequence of emissions from a reactive source, or signals a `java.util.NoSuchElementException` if the specified index is not within the sequence. + +### elementAtOrError example + +```java +Observable source = Observable.just("Kirk", "Spock", "Chekov", "Sulu"); +Single element = source.elementAtOrError(4); + +element.subscribe( + name -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## filter + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/filter.html](http://reactivex.io/documentation/operators/filter.html) + +Filters items emitted by a reactive source by only emitting those that satisfy a specified predicate. + +### filter example + +```java +Observable.just(1, 2, 3, 4, 5, 6) + .filter(x -> x % 2 == 0) + .subscribe(System.out::println); + +// prints: +// 2 +// 4 +// 6 +``` + +## first + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or emits the given default item if the source completes without emitting an item. This differs from [`firstElement`](#firstelement) in that this operator returns a `Single` whereas [`firstElement`](#firstelement) returns a `Maybe`. + +### first example + +```java +Observable source = Observable.just("A", "B", "C"); +Single firstOrDefault = source.first("D"); + +firstOrDefault.subscribe(System.out::println); + +// prints A +``` + +## firstElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or just completes if the source completes without emitting an item. This differs from [`first`](#first) in that this operator returns a `Maybe` whereas [`first`](#first) returns a `Single`. + +### firstElement example + +```java +Observable source = Observable.just("A", "B", "C"); +Maybe first = source.firstElement(); + +first.subscribe(System.out::println); + +// prints A +``` + +## firstOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/first.html](http://reactivex.io/documentation/operators/first.html) + +Emits only the first item emitted by a reactive source, or signals a `java.util.NoSuchElementException` if the source completes without emitting an item. + +### firstOrError example + +```java +Observable emptySource = Observable.empty(); +Single firstOrError = emptySource.firstOrError(); + +firstOrError.subscribe( + element -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## ignoreElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/ignoreelements.html](http://reactivex.io/documentation/operators/ignoreelements.html) + +Ignores the single item emitted by a `Single` or `Maybe` source, and returns a `Completable` that signals only the error or completion event from the the source. + +### ignoreElement example + +```java +Single source = Single.timer(1, TimeUnit.SECONDS); +Completable completable = source.ignoreElement(); + +completable.doOnComplete(() -> System.out.println("Done!")) + .blockingAwait(); + +// prints (after 1 second): +// Done! +``` + +## ignoreElements + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/ignoreelements.html](http://reactivex.io/documentation/operators/ignoreelements.html) + +Ignores all items from the `Observable` or `Flowable` source, and returns a `Completable` that signals only the error or completion event from the source. + +### ignoreElements example + +```java +Observable source = Observable.intervalRange(1, 5, 1, 1, TimeUnit.SECONDS); +Completable completable = source.ignoreElements(); + +completable.doOnComplete(() -> System.out.println("Done!")) + .blockingAwait(); + +// prints (after 5 seconds): +// Done! +``` + +## last + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or emits the given default item if the source completes without emitting an item. This differs from [`lastElement`](#lastelement) in that this operator returns a `Single` whereas [`lastElement`](#lastelement) returns a `Maybe`. + +### last example + +```java +Observable source = Observable.just("A", "B", "C"); +Single lastOrDefault = source.last("D"); + +lastOrDefault.subscribe(System.out::println); + +// prints C +``` + +## lastElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or just completes if the source completes without emitting an item. This differs from [`last`](#last) in that this operator returns a `Maybe` whereas [`last`](#last) returns a `Single`. + +### lastElement example + +```java +Observable source = Observable.just("A", "B", "C"); +Maybe last = source.lastElement(); + +last.subscribe(System.out::println); + +// prints C +``` + +## lastOrError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/last.html](http://reactivex.io/documentation/operators/last.html) + +Emits only the last item emitted by a reactive source, or signals a `java.util.NoSuchElementException` if the source completes without emitting an item. + +### lastOrError example + +```java +Observable emptySource = Observable.empty(); +Single lastOrError = emptySource.lastOrError(); + +lastOrError.subscribe( + element -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: " + error)); + +// prints: +// onError: java.util.NoSuchElementException +``` + +## ofType + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/filter.html](http://reactivex.io/documentation/operators/filter.html) + +Filters items emitted by a reactive source by only emitting those of the specified type. + +### ofType example + +```java +Observable numbers = Observable.just(1, 4.0, 3, 2.71, 2f, 7); +Observable integers = numbers.ofType(Integer.class); + +integers.subscribe((Integer x) -> System.out.println(x)); + +// prints: +// 1 +// 3 +// 7 +``` + +## sample + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Filters items emitted by a reactive source by only emitting the most recently emitted item within periodic time intervals. + +### sample example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -0s-----c--1s---d----2s-|--> +// -----------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .sample(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: C +// onNext: D +// onComplete +``` + +## skip + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/skip.html](http://reactivex.io/documentation/operators/skip.html) + +Drops the first *n* items emitted by a reactive source, and emits the remaining items. + +### skip example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.skip(4) + .subscribe(System.out::println); + +// prints: +// 5 +// 6 +// 7 +// 8 +// 9 +// 10 +``` + +## skipLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/skiplast.html](http://reactivex.io/documentation/operators/skiplast.html) + +Drops the last *n* items emitted by a reactive source, and emits the remaining items. + +### skipLast example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.skipLast(4) + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +``` + +## take + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/take.html](http://reactivex.io/documentation/operators/take.html) + +Emits only the first *n* items emitted by a reactive source. + +### take example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.take(4) + .subscribe(System.out::println); + +// prints: +// 1 +// 2 +// 3 +// 4 +``` + +## takeLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/takelast.html](http://reactivex.io/documentation/operators/takelast.html) + +Emits only the last *n* items emitted by a reactive source. + +### takeLast example + +```java +Observable source = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + +source.takeLast(4) + .subscribe(System.out::println); + +// prints: +// 7 +// 8 +// 9 +// 10 +``` + +## throttleFirst + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits only the first item emitted by a reactive source during sequential time windows of a specified duration. + +### throttleFirst example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// a---------1s +// d-------|--> +// -A--------------D-------|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleFirst(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onComplete +``` + +## throttleLast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits only the last item emitted by a reactive source during sequential time windows of a specified duration. + +### throttleLast example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -0s-----c--1s---d----2s-|--> +// -----------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleLast(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: C +// onNext: D +// onComplete +``` + +## throttleLatest + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sample.html](http://reactivex.io/documentation/operators/sample.html) + +Emits the next item emitted by a reactive source, then periodically emits the latest item (if any) when the specified timeout elapses between them. + +### throttleLatest example + +```java +// Diagram: +// -A----B-C-------D-----E-|--> +// -a------c--1s +// -----d----1s +// -e-|--> +// -A---------C---------D--|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(500); + emitter.onNext("B"); + + Thread.sleep(200); + emitter.onNext("C"); + + Thread.sleep(800); + emitter.onNext("D"); + + Thread.sleep(600); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleLatest(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: C +// onNext: D +// onComplete +``` + +## throttleWithTimeout + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/debounce.html](http://reactivex.io/documentation/operators/debounce.html) + +> Alias to [debounce](#debounce) + +Drops items emitted by a reactive source that are followed by newer items before the given timeout value expires. The timer resets on each emission. + +### throttleWithTimeout example + +```java +// Diagram: +// -A--------------B----C-D-------------------E-|----> +// a---------1s +// b---------1s +// c---------1s +// d---------1s +// e-|----> +// -----------A---------------------D-----------E-|--> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(1_500); + emitter.onNext("B"); + + Thread.sleep(500); + emitter.onNext("C"); + + Thread.sleep(250); + emitter.onNext("D"); + + Thread.sleep(2_000); + emitter.onNext("E"); + emitter.onComplete(); +}); + +source.subscribeOn(Schedulers.io()) + .throttleWithTimeout(1, TimeUnit.SECONDS) + .blockingSubscribe( + item -> System.out.println("onNext: " + item), + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// onNext: A +// onNext: D +// onNext: E +// onComplete +``` + +## timeout + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/timeout.html](http://reactivex.io/documentation/operators/timeout.html) + +Emits the items from the `Observable` or `Flowable` source, but terminates with a `java.util.concurrent.TimeoutException` if the next item is not emitted within the specified timeout duration starting from the previous item. For `Maybe`, `Single` and `Completable` the specified timeout duration specifies the maximum time to wait for a success or completion event to arrive. If the `Maybe`, `Single` or `Completable` does not complete within the given time a `java.util.concurrent.TimeoutException` will be emitted. + +### timeout example + +```java +// Diagram: +// -A-------B---C-----------D-|--> +// a---------1s +// b---------1s +// c---------1s +// -A-------B---C---------X------> + +Observable source = Observable.create(emitter -> { + emitter.onNext("A"); + + Thread.sleep(800); + emitter.onNext("B"); + + Thread.sleep(400); + emitter.onNext("C"); + + Thread.sleep(1200); + emitter.onNext("D"); + emitter.onComplete(); +}); + +source.timeout(1, TimeUnit.SECONDS) + .subscribe( + item -> System.out.println("onNext: " + item), + error -> System.out.println("onError: " + error), + () -> System.out.println("onComplete will not be printed!")); + +// prints: +// onNext: A +// onNext: B +// onNext: C +// onError: java.util.concurrent.TimeoutException: The source did not signal an event for 1 seconds and has been terminated. +``` diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md new file mode 100644 index 0000000000..fb9baa47dd --- /dev/null +++ b/docs/Getting-Started.md @@ -0,0 +1,126 @@ +## Getting Binaries + +You can find binaries and dependency information for Maven, Ivy, Gradle, SBT, and others at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A"io.reactivex.rxjava2"%20AND%20"rxjava2"). + +Example for Maven: + +```xml + + io.reactivex.rxjava2 + rxjava + 2.2.0 + +``` +and for Ivy: + +```xml + +``` + +and for SBT: + +```scala +libraryDependencies += "io.reactivex" %% "rxscala" % "0.26.5" + +libraryDependencies += "io.reactivex.rxjava2" % "rxjava" % "2.2.0" +``` + +and for Gradle: +```groovy +compile 'io.reactivex.rxjava2:rxjava:2.2.0' +``` + +If you need to download the jars instead of using a build system, create a Maven `pom` file like this with the desired version: + +```xml + + + 4.0.0 + io.reactivex.rxjava2 + rxjava + 2.2.0 + RxJava + Reactive Extensions for Java + https://github.com/ReactiveX/RxJava + + + io.reactivex.rxjava2 + rxjava + 2.2.0 + + + +``` + +Then execute: + +``` +$ mvn -f download-rxjava-pom.xml dependency:copy-dependencies +``` + +That command downloads `rxjava-*.jar` and its dependencies into `./target/dependency/`. + +You need Java 6 or later. + +### Snapshots + +Snapshots are available via [JFrog](https://oss.jfrog.org/libs-snapshot/io/reactivex/rxjava2/rxjava/): + +```groovy +repositories { + maven { url 'https://oss.jfrog.org/libs-snapshot' } +} + +dependencies { + compile 'io.reactivex.rxjava2:rxjava:2.2.0-SNAPSHOT' +} +``` + +## Building + +To check out and build the RxJava source, issue the following commands: + +``` +$ git clone git@github.com:ReactiveX/RxJava.git +$ cd RxJava/ +$ ./gradlew build +``` + +To do a clean build, issue the following command: + +``` +$ ./gradlew clean build +``` + +A build should look similar to this: + +``` +$ ./gradlew build +:rxjava:compileJava +:rxjava:processResources UP-TO-DATE +:rxjava:classes +:rxjava:jar +:rxjava:sourcesJar +:rxjava:signArchives SKIPPED +:rxjava:assemble +:rxjava:licenseMain UP-TO-DATE +:rxjava:licenseTest UP-TO-DATE +:rxjava:compileTestJava +:rxjava:processTestResources UP-TO-DATE +:rxjava:testClasses +:rxjava:test +:rxjava:check +:rxjava:build + +BUILD SUCCESSFUL + +Total time: 30.758 secs +``` + +On a clean build you will see the unit tests run. They will look something like this: + +``` +> Building > :rxjava:test > 91 tests completed +``` diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 0000000000..474c8edc77 --- /dev/null +++ b/docs/Home.md @@ -0,0 +1,24 @@ +RxJava is a Java VM implementation of [ReactiveX (Reactive Extensions)](https://reactivex.io): a library for composing asynchronous and event-based programs by using observable sequences. + +For more information about ReactiveX, see the [Introduction to ReactiveX](http://reactivex.io/intro.html) page. + +### RxJava is Lightweight + +RxJava tries to be very lightweight. It is implemented as a single JAR that is focused on just the Observable abstraction and related higher-order functions. + +### RxJava is a Polyglot Implementation + +RxJava supports Java 6 or higher and JVM-based languages such as [Groovy](https://github.com/ReactiveX/RxGroovy), [Clojure](https://github.com/ReactiveX/RxClojure), [JRuby](https://github.com/ReactiveX/RxJRuby), [Kotlin](https://github.com/ReactiveX/RxKotlin) and [Scala](https://github.com/ReactiveX/RxScala). + +RxJava is meant for a more polyglot environment than just Java/Scala, and it is being designed to respect the idioms of each JVM-based language. (This is something we’re still working on.) + +### RxJava Libraries + +The following external libraries can work with RxJava: + +* [Hystrix](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Reactive-Execution) latency and fault tolerance bulkheading library. +* [Camel RX](http://camel.apache.org/rx.html) provides an easy way to reuse any of the [Apache Camel components, protocols, transports and data formats](http://camel.apache.org/components.html) with the RxJava API +* [rxjava-http-tail](https://github.com/myfreeweb/rxjava-http-tail) allows you to follow logs over HTTP, like `tail -f` +* [mod-rxvertx - Extension for VertX](https://github.com/vert-x/mod-rxvertx) that provides support for Reactive Extensions (RX) using the RxJava library +* [rxjava-jdbc](https://github.com/davidmoten/rxjava-jdbc) - use RxJava with jdbc connections to stream ResultSets and do functional composition of statements +* [rtree](https://github.com/davidmoten/rtree) - immutable in-memory R-tree and R*-tree with RxJava api including backpressure \ No newline at end of file diff --git a/docs/How-To-Use-RxJava.md b/docs/How-To-Use-RxJava.md new file mode 100644 index 0000000000..16d156caa9 --- /dev/null +++ b/docs/How-To-Use-RxJava.md @@ -0,0 +1,405 @@ +# Hello World! + +The following sample implementations of “Hello World” in Java, Groovy, Clojure, and Scala create an Observable from a list of Strings, and then subscribe to this Observable with a method that prints “Hello _String_!” for each string emitted by the Observable. + +You can find additional code examples in the `/src/examples` folders of each [language adaptor](https://github.com/ReactiveX/): + +* [RxGroovy examples](https://github.com/ReactiveX/RxGroovy/tree/1.x/src/examples/groovy/rx/lang/groovy/examples) +* [RxClojure examples](https://github.com/ReactiveX/RxClojure/tree/0.x/src/examples/clojure/rx/lang/clojure/examples) +* [RxScala examples](https://github.com/ReactiveX/RxScala/tree/0.x/examples/src/main/scala) + +### Java + +```java +public static void hello(String... args) { + Flowable.fromArray(args).subscribe(s -> System.out.println("Hello " + s + "!")); +} +``` + +If your platform doesn't support Java 8 lambdas (yet), you have to create an inner class of ```Consumer``` manually: +```java +public static void hello(String... args) { + Flowable.fromArray(args).subscribe(new Consumer() { + @Override + public void accept(String s) { + System.out.println("Hello " + s + "!"); + } + }); +} +``` + +```java +hello("Ben", "George"); +Hello Ben! +Hello George! +``` + +### Groovy + +```groovy +def hello(String[] names) { + Observable.from(names).subscribe { println "Hello ${it}!" } +} +``` + +```groovy +hello("Ben", "George") +Hello Ben! +Hello George! +``` + +### Clojure + +```clojure +(defn hello + [&rest] + (-> (Observable/from &rest) + (.subscribe #(println (str "Hello " % "!"))))) +``` + +``` +(hello ["Ben" "George"]) +Hello Ben! +Hello George! +``` +### Scala + +```scala +import rx.lang.scala.Observable + +def hello(names: String*) { + Observable.from(names) subscribe { n => + println(s"Hello $n!") + } +} +``` + +```scala +hello("Ben", "George") +Hello Ben! +Hello George! +``` + +# How to Design Using RxJava + +To use RxJava you create Observables (which emit data items), transform those Observables in various ways to get the precise data items that interest you (by using Observable operators), and then observe and react to these sequences of interesting items (by implementing Observers or Subscribers and then subscribing them to the resulting transformed Observables). + +## Creating Observables + +To create an Observable, you can either implement the Observable's behavior manually by passing a function to [`create( )`](http://reactivex.io/documentation/operators/create.html) that exhibits Observable behavior, or you can convert an existing data structure into an Observable by using [some of the Observable operators that are designed for this purpose](Creating-Observables). + +### Creating an Observable from an Existing Data Structure + +You use the Observable [`just( )`](http://reactivex.io/documentation/operators/just.html) and [`from( )`](http://reactivex.io/documentation/operators/from.html) methods to convert objects, lists, or arrays of objects into Observables that emit those objects: + +```groovy +Observable o = Observable.from("a", "b", "c"); + +def list = [5, 6, 7, 8] +Observable o = Observable.from(list); + +Observable o = Observable.just("one object"); +``` + +These converted Observables will synchronously invoke the [`onNext( )`](Observable#onnext-oncompleted-and-onerror) method of any subscriber that subscribes to them, for each item to be emitted by the Observable, and will then invoke the subscriber’s [`onCompleted( )`](Observable#onnext-oncompleted-and-onerror) method. + +### Creating an Observable via the `create( )` method + +You can implement asynchronous i/o, computational operations, or even “infinite” streams of data by designing your own Observable and implementing it with the [`create( )`](http://reactivex.io/documentation/operators/create.html) method. + +#### Synchronous Observable Example + +```groovy +/** + * This example shows a custom Observable that blocks + * when subscribed to (does not spawn an extra thread). + */ +def customObservableBlocking() { + return Observable.create { aSubscriber -> + 50.times { i -> + if (!aSubscriber.unsubscribed) { + aSubscriber.onNext("value_${i}") + } + } + // after sending all values we complete the sequence + if (!aSubscriber.unsubscribed) { + aSubscriber.onCompleted() + } + } +} + +// To see output: +customObservableBlocking().subscribe { println(it) } +``` + +#### Asynchronous Observable Example + +The following example uses Groovy to create an Observable that emits 75 strings. + +It is written verbosely, with static typing and implementation of the `Func1` anonymous inner class, to make the example more clear: + +```groovy +/** + * This example shows a custom Observable that does not block + * when subscribed to as it spawns a separate thread. + */ +def customObservableNonBlocking() { + return Observable.create({ subscriber -> + Thread.start { + for (i in 0..<75) { + if (subscriber.unsubscribed) { + return + } + subscriber.onNext("value_${i}") + } + // after sending all values we complete the sequence + if (!subscriber.unsubscribed) { + subscriber.onCompleted() + } + } + } as Observable.OnSubscribe) +} + +// To see output: +customObservableNonBlocking().subscribe { println(it) } +``` + +Here is the same code in Clojure that uses a Future (instead of raw thread) and is implemented more consisely: + +```clojure +(defn customObservableNonBlocking [] + "This example shows a custom Observable that does not block + when subscribed to as it spawns a separate thread. + + returns Observable" + (Observable/create + (fn [subscriber] + (let [f (future + (doseq [x (range 50)] (-> subscriber (.onNext (str "value_" x)))) + ; after sending all values we complete the sequence + (-> subscriber .onCompleted)) + )) + )) +``` + +```clojure +; To see output +(.subscribe (customObservableNonBlocking) #(println %)) +``` + +Here is an example that fetches articles from Wikipedia and invokes onNext with each one: + +```clojure +(defn fetchWikipediaArticleAsynchronously [wikipediaArticleNames] + "Fetch a list of Wikipedia articles asynchronously. + + return Observable of HTML" + (Observable/create + (fn [subscriber] + (let [f (future + (doseq [articleName wikipediaArticleNames] + (-> subscriber (.onNext (http/get (str "http://en.wikipedia.org/wiki/" articleName))))) + ; after sending response to onnext we complete the sequence + (-> subscriber .onCompleted)) + )))) +``` + +```clojure +(-> (fetchWikipediaArticleAsynchronously ["Tiger" "Elephant"]) + (.subscribe #(println "--- Article ---\n" (subs (:body %) 0 125) "..."))) +``` + +Back to Groovy, the same Wikipedia functionality but using closures instead of anonymous inner classes: + +```groovy +/* + * Fetch a list of Wikipedia articles asynchronously. + */ +def fetchWikipediaArticleAsynchronously(String... wikipediaArticleNames) { + return Observable.create { subscriber -> + Thread.start { + for (articleName in wikipediaArticleNames) { + if (subscriber.unsubscribed) { + return + } + subscriber.onNext(new URL("http://en.wikipedia.org/wiki/${articleName}").text) + } + if (!subscriber.unsubscribed) { + subscriber.onCompleted() + } + } + return subscriber + } +} + +fetchWikipediaArticleAsynchronously("Tiger", "Elephant") + .subscribe { println "--- Article ---\n${it.substring(0, 125)}" } +``` + +Results: + +```text +--- Article --- + + + +Tiger - Wikipedia, the free encyclopedia ... +--- Article --- + + + +Elephant - Wikipedia, the free encyclopedia</tit ... +``` + +Note that all of the above examples ignore error handling, for brevity. See below for examples that include error handling. + +More information can be found on the [[Observable]] and [[Creating Observables|Creating-Observables]] pages. + +## Transforming Observables with Operators + +RxJava allows you to chain _operators_ together to transform and compose Observables. + +The following example, in Groovy, uses a previously defined, asynchronous Observable that emits 75 items, skips over the first 10 of these ([`skip(10)`](http://reactivex.io/documentation/operators/skip.html)), then takes the next 5 ([`take(5)`](http://reactivex.io/documentation/operators/take.html)), and transforms them ([`map(...)`](http://reactivex.io/documentation/operators/map.html)) before subscribing and printing the items: + +```groovy +/** + * Asynchronously calls 'customObservableNonBlocking' and defines + * a chain of operators to apply to the callback sequence. + */ +def simpleComposition() { + customObservableNonBlocking().skip(10).take(5) + .map({ stringValue -> return stringValue + "_xform"}) + .subscribe({ println "onNext => " + it}) +} +``` + +This results in: + +```text +onNext => value_10_xform +onNext => value_11_xform +onNext => value_12_xform +onNext => value_13_xform +onNext => value_14_xform +``` + +Here is a marble diagram that illustrates this transformation: + +<img src="/Netflix/RxJava/wiki/images/rx-operators/Composition.1.png" width="640" height="536" /> + +This next example, in Clojure, consumes three asynchronous Observables, including a dependency from one to another, and emits a single response item by combining the items emitted by each of the three Observables with the [`zip`](http://reactivex.io/documentation/operators/zip.html) operator and then transforming the result with [`map`](http://reactivex.io/documentation/operators/map.html): + +```clojure +(defn getVideoForUser [userId videoId] + "Get video metadata for a given userId + - video metadata + - video bookmark position + - user data + return Observable<Map>" + (let [user-observable (-> (getUser userId) + (.map (fn [user] {:user-name (:name user) :language (:preferred-language user)}))) + bookmark-observable (-> (getVideoBookmark userId videoId) + (.map (fn [bookmark] {:viewed-position (:position bookmark)}))) + ; getVideoMetadata requires :language from user-observable so nest inside map function + video-metadata-observable (-> user-observable + (.mapMany + ; fetch metadata after a response from user-observable is received + (fn [user-map] + (getVideoMetadata videoId (:language user-map)))))] + ; now combine 3 observables using zip + (-> (Observable/zip bookmark-observable video-metadata-observable user-observable + (fn [bookmark-map metadata-map user-map] + {:bookmark-map bookmark-map + :metadata-map metadata-map + :user-map user-map})) + ; and transform into a single response object + (.map (fn [data] + {:video-id videoId + :video-metadata (:metadata-map data) + :user-id userId + :language (:language (:user-map data)) + :bookmark (:viewed-position (:bookmark-map data)) + }))))) +``` + +The response looks like this: + +```clojure +{:video-id 78965, + :video-metadata {:video-id 78965, :title House of Cards: Episode 1, + :director David Fincher, :duration 3365}, + :user-id 12345, :language es-us, :bookmark 0} +``` + +And here is a marble diagram that illustrates how that code produces that response: + +<img src="/Netflix/RxJava/wiki/images/rx-operators/Composition.2.png" width="640" height="742" /> + +The following example, in Groovy, comes from [Ben Christensen’s QCon presentation on the evolution of the Netflix API](https://speakerdeck.com/benjchristensen/evolution-of-the-netflix-api-qcon-sf-2013). It combines two Observables with the [`merge`](http://reactivex.io/documentation/operators/merge.html) operator, then uses the [`reduce`](http://reactivex.io/documentation/operators/reduce.html) operator to construct a single item out of the resulting sequence, then transforms that item with [`map`](http://reactivex.io/documentation/operators/map.html) before emitting it: + +```groovy +public Observable getVideoSummary(APIVideo video) { + def seed = [id:video.id, title:video.getTitle()]; + def bookmarkObservable = getBookmark(video); + def artworkObservable = getArtworkImageUrl(video); + return( Observable.merge(bookmarkObservable, artworkObservable) + .reduce(seed, { aggregate, current -> aggregate << current }) + .map({ [(video.id.toString() : it] })) +} +``` + +And here is a marble diagram that illustrates how that code uses the [`reduce`](http://reactivex.io/documentation/operators/reduce.html) operator to bring the results from multiple Observables together in one structure: + +<img src="/Netflix/RxJava/wiki/images/rx-operators/Composition.3.png" width="640" height="640" /> + +## Error Handling + +Here is a version of the Wikipedia example from above revised to include error handling: + +```groovy +/* + * Fetch a list of Wikipedia articles asynchronously, with error handling. + */ +def fetchWikipediaArticleAsynchronouslyWithErrorHandling(String... wikipediaArticleNames) { + return Observable.create({ subscriber -> + Thread.start { + try { + for (articleName in wikipediaArticleNames) { + if (true == subscriber.isUnsubscribed()) { + return; + } + subscriber.onNext(new URL("http://en.wikipedia.org/wiki/"+articleName).getText()); + } + if (false == subscriber.isUnsubscribed()) { + subscriber.onCompleted(); + } + } catch(Throwable t) { + if (false == subscriber.isUnsubscribed()) { + subscriber.onError(t); + } + } + return (subscriber); + } + }); +} +``` + +Notice how it now invokes [`onError(Throwable t)`](Observable#onnext-oncompleted-and-onerror) if an error occurs and note that the following code passes [`subscribe()`](http://reactivex.io/documentation/operators/subscribe.html) a second method that handles `onError`: + +```groovy +fetchWikipediaArticleAsynchronouslyWithErrorHandling("Tiger", "NonExistentTitle", "Elephant") + .subscribe( + { println "--- Article ---\n" + it.substring(0, 125) }, + { println "--- Error ---\n" + it.getMessage() }) +``` + +See the [Error-Handling-Operators](Error-Handling-Operators) page for more information on specialized error handling techniques in RxJava, including methods like [`onErrorResumeNext()`](http://reactivex.io/documentation/operators/catch.html) and [`onErrorReturn()`](http://reactivex.io/documentation/operators/catch.html) that allow Observables to continue with fallbacks in the event that they encounter errors. + +Here is an example of how you can use such a method to pass along custom information about any exceptions you encounter. Imagine you have an Observable or cascade of Observables — `myObservable` — and you want to intercept any exceptions that would normally pass through to an Subscriber’s `onError` method, replacing these with a customized Throwable of your own design. You could do this by modifying `myObservable` with the [`onErrorResumeNext()`](http://reactivex.io/documentation/operators/catch.html) method, and passing into that method an Observable that calls `onError` with your customized Throwable (a utility method called [`error()`](http://reactivex.io/documentation/operators/empty-never-throw.html) will generate such an Observable for you): + +```groovy +myModifiedObservable = myObservable.onErrorResumeNext({ t -> + Throwable myThrowable = myCustomizedThrowableCreator(t); + return (Observable.error(myThrowable)); +}); +``` diff --git a/docs/How-to-Contribute.md b/docs/How-to-Contribute.md new file mode 100644 index 0000000000..d7c4a3ceb7 --- /dev/null +++ b/docs/How-to-Contribute.md @@ -0,0 +1,41 @@ +RxJava is still a work in progress and has a long list of work documented in the [Issues](https://github.com/ReactiveX/RxJava/issues). + + +If you wish to contribute we would ask that you: +- read [Rx Design Guidelines](http://blogs.msdn.com/b/rxteam/archive/2010/10/28/rx-design-guidelines.aspx) +- review existing code and comply with existing patterns and idioms +- include unit tests +- stick to Rx contracts as defined by the Rx.Net implementation when porting operators (each issue attempts to reference the correct documentation from MSDN) + +Information about licensing can be found at: [CONTRIBUTING](https://github.com/ReactiveX/RxJava/blob/2.x/CONTRIBUTING.md). + +## How to import the project into Eclipse +Two options below: + +### Import as Eclipse project + + ./gradlew eclipse + +In Eclipse +* choose File - Import - General - Existing Projects into Workspace +* Browse to RxJava folder +* click Finish. +* Right click on the project in Package Explorer, select Properties - Java Compiler - Errors/Warnings - click Enable project specific settings. +* Still in Errors/Warnings, go to Deprecated and restricted API and set Forbidden reference (access-rules) to Warning. + +### Import as Gradle project + +You need the Gradle plugin for Eclipse installed. + +In Eclipse +* choose File - Import - Gradle - Gradle Project. +* Browse to RxJava folder +* click Build Model +* select the project +* click Finish + + + + + + diff --git a/docs/Implementing-Your-Own-Operators.md b/docs/Implementing-Your-Own-Operators.md new file mode 100644 index 0000000000..9e4a165f8b --- /dev/null +++ b/docs/Implementing-Your-Own-Operators.md @@ -0,0 +1,114 @@ +You can implement your own Observable operators. This page shows you how. + +If your operator is designed to *originate* an Observable, rather than to transform or react to a source Observable, use the [`create( )`](http://reactivex.io/documentation/operators/create.html) method rather than trying to implement `Observable` manually. Otherwise, you can create a custom operator by following the instructions on this page. + +If your operator is designed to act on the individual items emitted by a source Observable, follow the instructions under [_Sequence Operators_](Implementing-Your-Own-Operators#sequence-operators) below. If your operator is designed to transform the source Observable as a whole (for instance, by applying a particular set of existing RxJava operators to it) follow the instructions under [_Transformational Operators_](Implementing-Your-Own-Operators#transformational-operators) below. + +(**Note:** in Xtend, a Groovy-like language, you can implement your operators as _extension methods_ and can thereby chain them directly without using the methods described on this page. See [RxJava and Xtend](http://mnmlst-dvlpr.blogspot.de/2014/07/rxjava-and-xtend.html) for details.) + +# Sequence Operators + +The following example shows how you can use the `lift( )` operator to chain your custom operator (in this example: `myOperator`) alongside standard RxJava operators like `ofType` and `map`: +```groovy +fooObservable = barObservable.ofType(Integer).map({it*2}).lift(new myOperator<T>()).map({"transformed by myOperator: " + it}); +``` +The following section shows how you form the scaffolding of your operator so that it will work correctly with `lift( )`. + +## Implementing Your Operator + +Define your operator as a public class that implements the [`Operator`](http://reactivex.io/RxJava/javadoc/rx/Observable.Operator.html) interface, like so: +```java +public class myOperator<T> implements Operator<T> { + public myOperator( /* any necessary params here */ ) { + /* any necessary initialization here */ + } + + @Override + public Subscriber<? super T> call(final Subscriber<? super T> s) { + return new Subscriber<t>(s) { + @Override + public void onCompleted() { + /* add your own onCompleted behavior here, or just pass the completed notification through: */ + if(!s.isUnsubscribed()) { + s.onCompleted(); + } + } + + @Override + public void onError(Throwable t) { + /* add your own onError behavior here, or just pass the error notification through: */ + if(!s.isUnsubscribed()) { + s.onError(t); + } + } + + @Override + public void onNext(T item) { + /* this example performs some sort of operation on each incoming item and emits the results */ + if(!s.isUnsubscribed()) { + transformedItem = myOperatorTransformOperation(item); + s.onNext(transformedItem); + } + } + }; + } +} +``` + +# Transformational Operators + +The following example shows how you can use the `compose( )` operator to chain your custom operator (in this example, an operator called `myTransformer` that transforms an Observable that emits Integers into one that emits Strings) alongside standard RxJava operators like `ofType` and `map`: +```groovy +fooObservable = barObservable.ofType(Integer).map({it*2}).compose(new myTransformer<Integer,String>()).map({"transformed by myOperator: " + it}); +``` +The following section shows how you form the scaffolding of your operator so that it will work correctly with `compose( )`. + +## Implementing Your Transformer + +Define your transforming function as a public class that implements the [`Transformer`](http://reactivex.io/RxJava/javadoc/rx/Observable.Transformer.html) interface, like so: + +````java +public class myTransformer<Integer,String> implements Transformer<Integer,String> { + public myTransformer( /* any necessary params here */ ) { + /* any necessary initialization here */ + } + + @Override + public Observable<String> call(Observable<Integer> source) { + /* + * this simple example Transformer applies map() to the source Observable + * in order to transform the "source" observable from one that emits + * integers to one that emits string representations of those integers. + */ + return source.map( new Func1<Integer,String>() { + @Override + public String call(Integer t1) { + return String.valueOf(t1); + } + } ); + } +} +```` + +## See also + +* [“Don’t break the chain: use RxJava’s compose() operator”](http://blog.danlew.net/2015/03/02/dont-break-the-chain/) by Dan Lew + +# Other Considerations + +* Your sequence operator may want to check [its Subscriber’s `isUnsubscribed( )` status](Observable#unsubscribing) before it emits any item to (or sends any notification to) the Subscriber. There’s no need to waste time generating items that no Subscriber is interested in seeing. +* Take care that your sequence operator obeys the core tenets of the Observable contract: + * It may call a Subscriber’s [`onNext( )`](Observable#onnext-oncompleted-and-onerror) method any number of times, but these calls must be non-overlapping. + * It may call either a Subscriber’s [`onCompleted( )`](Observable#onnext-oncompleted-and-onerror) or [`onError( )`](Observable#onnext-oncompleted-and-onerror) method, but not both, exactly once, and it may not subsequently call a Subscriber’s [`onNext( )`](Observable#onnext-oncompleted-and-onerror) method. + * If you are unable to guarantee that your operator conforms to the above two tenets, you can add the [`serialize( )`](Observable-Utility-Operators#serialize) operator to it, which will force the correct behavior. +* Keep an eye on [Issue #1962](https://github.com/ReactiveX/RxJava/issues/1962) — there are plans to create a test scaffold that you can use to write tests which verify that your new operator conforms to the Observable contract. +* Do not block within your operator. +* When possible, you should compose new operators by combining existing operators, rather than implementing them with new code. RxJava itself does this with some of its standard operators, for example: + * [`first( )`](http://reactivex.io/documentation/operators/first.html) is defined as <tt>[take(1)](http://reactivex.io/documentation/operators/take.html).[single( )](http://reactivex.io/documentation/operators/first.html)</tt> + * [`ignoreElements( )`](http://reactivex.io/documentation/operators/ignoreelements.html) is defined as <tt>[filter(alwaysFalse( ))](http://reactivex.io/documentation/operators/filter.html)</tt> + * [`reduce(a)`](http://reactivex.io/documentation/operators/reduce.html) is defined as <tt>[scan(a)](http://reactivex.io/documentation/operators/scan.html).[last( )](http://reactivex.io/documentation/operators/last.html)</tt> +* If your operator uses functions or lambdas that are passed in as parameters (predicates, for instance), note that these may be sources of exceptions, and be prepared to catch these and notify subscribers via `onError( )` calls. + * Some exceptions are considered “fatal” and for them there’s no point in trying to call `onError( )` because that will either be futile or will just compound the problem. You can use the `Exceptions.throwIfFatal(throwable)` method to filter out such fatal exceptions and rethrow them rather than try to notify about them. +* In general, notify subscribers of error conditions immediately, rather than making an effort to emit more items first. +* Be aware that “<code>null</code>” is a valid item that may be emitted by an Observable. A frequent source of bugs is to test some variable meant to hold an emitted item against <code>null</code> as a substitute for testing whether or not an item was emitted. An emission of “<code>null</code>” is still an emission and is not the same as not emitting anything. +* It can be tricky to make your operator behave well in *backpressure* scenarios. See [Advanced RxJava](http://akarnokd.blogspot.hu/), a blog from Dávid Karnok, for a good discussion of the factors at play and how to deal with them. \ No newline at end of file diff --git a/docs/Implementing-custom-operators-(draft).md b/docs/Implementing-custom-operators-(draft).md new file mode 100644 index 0000000000..a95b6968f0 --- /dev/null +++ b/docs/Implementing-custom-operators-(draft).md @@ -0,0 +1,508 @@ +# Introduction + +RxJava features over 100 operators to support the most common reactive dataflow patterns. Generally, there exist a combination of operators, typically `flatMap`, `defer` and `publish`, that allow composing less common patterns with standard guarantees. When you have an uncommon pattern and you can't seem to find the right operators, try asking about it on our issue list (or Stackoverflow) first. + +If none of this applies to your use case, you may want to implement a custom operator. Be warned that **writing operators is hard**: when one writes an operator, the `Observable` **protocol**, **unsubscription**, **backpressure** and **concurrency** have to be taken into account and adhered to the letter. + +*Note that this page uses Java 8 syntax for brevity.* + +# Considerations + +## Observable protocol + +The `Observable` protocol states that you have to call the `Observer` methods, `onNext`, `onError` and `onCompleted` in a sequential manner. In other words, these can't be called concurrently and have to be **serialized**. The `SerializedObserver` and `SerializedSubscriber` wrappers help you with these. Note that there are cases where this serialization has to happen. + +In addition, there is an expected pattern of method calls on `Observer`: + +``` +onNext* (onError | onCompleted)? +``` + +A custom operator has to honor this pattern on its push side as well. For example, if your operator turns an `onNext` into an `onError`, the upstream has to be stopped and no further methods can be called on the dowstream. + +## Unsubscription + +The basic `Observer` method has no direct means to signal to the upstream source to stop emitting events. One either has to get the `Subscription` that the `Observable.subscribe(Observer<T>)` returns **and** be asynchronous itself. + +This shortcoming was resolved by introducing the `Subscriber` class that implements the `Subscription` interface. The interface allows detecting if a `Subscriber` is no longer interested in the events. + +```java +interface Subscription { + boolean isUnsubscribed(); + + void unsubscribe(); +} +``` + +In an operator, this allows active checking of the `Subscriber` state before emitting an event. + +In some cases, one needs to react to the child unsubscribing immediately and not just before an emission. To support this case, the `Subscriber` class has an `add(Subscription)` method that let's the operator register `Subscription`s of its own which get unsubscribed when the downstream calls `Subscriber.unsubscribe()`. + +```java +InputStream in = ... + +child.add(Subscriptions.create(() -> { + try { + in.close(); + } catch (IOException ex) { + RxJavaHooks.onError(ex); + } +})); +``` + +## Backpressure + +The name of this feature is often misinterpreted. It is about telling the upstream how many `onNext` events the downstream is ready to receive. For example, if the downstream requests 5, the upstream can only call `onNext` 5 times. If the upstream can't produce 5 elements but 3, it should deliver that 3 element followed by an `onError` or `onCompleted` (depending on the operator's purpose). The requests are cumulative in the sense that if the downstream requests 5 and then 2, there is going to be 7 requests outstanding. + +Backpressure handling adds a great deal of complexity to most operators: one has to track how many elements the downstream requested, how many have been delivered (by usually subtracting from the request amount) and sometimes how many elements are still available (but can't be delivered without requests). In addition, the downstream can request from any thread and is not required to happen on the common thread where otherwise the `onXXX` methods are called. + +The backpressure 'channel' is established between the upstream and downstream via the `Producer` interface: + +```java +interface Producer { + void request(long n); +} +``` + +When an upstream supports backpressure, it will call the `Subscriber.setProducer(Producer)` method on its downstream `Subscriber` with the implementation of this interface. The downstream then can respond with `Long.MAX_VALUE` to start an unbounded streaming (effectively no backpressure between the immediate upstream and downstream) or any other positive value. A request amount of zero should be ignored. + +Protocol-vise, there is no strict time when a producer can be set and it may never appear. Operators have to be ready to deal with this situation and assume the upstream runs in unbounded mode (as if `Long.MAX_VALUE` was requested). + +Often, operators may implement `Producer` and `Subscription` in a single class to handle both requests and unsubscriptions from the downstream: + +```java +final class MyEmitter implements Producer, Subscription { + final Subscriber<Integer> subscriber; + + public MyEmitter(Subscriber<Integer> subscriber) { + this.subscriber = subscriber; + } + + @Override + public void request(long n) { + if (n > 0) { + subscriber.onCompleted(); + } + } + + @Override + public void unsubscribe() { + System.out.println("Unsubscribed"); + } + + @Override + public boolean isUnsubscribed() { + return true; + } +} + +MyEmitter emitter = new MyEmitter(child); + +child.add(emitter); +child.setProducer(emitter); +``` + +Unfortunately, you can't implement `Producer` on a `Subscriber` because of an API oversight: `Subscriber` has a protected final `request(long n)` method to perform **deferred requesting** (store and accumulate the local request amounts until `setProducer` is called). + +## Concurrency + +When writing operators, we mostly have to deal with concurrency via the standard Java concurrency primitives: `AtomicXXX` classes, volatile variables, `Queue`s, mutual exclusion, Executors, etc. + +### RxJava tools + +RxJava has a few support classes and utilities that let's one deal with concurrency inside operators. + +The first one, `BackpressureUtils` deals with managing the cumulative requested and produced element counts for an operator. Its `getAndAddRequested()` method takes an `AtomicLong`, accumulates request amounts atomically and makes sure they don't overflow `Long.MAX_VALUE`. Its pair `produced()` subtracts the amount operators have produced, thus when both are in play, the given `AtomicLong` holds the current outstanding request amount for the downstream. + +Operators sometimes have to switch between multiple sources. If a previous source didn't fulfill all its requested amount, the new source has to start with that unfulfilled amount. Otherwise as the downstream didn't receive the requested amount (and no terminal event either), it can't know when to request more. If this switch happens at an `Observable` boundary (think `concat`), the `ProducerArbiter` helps managing the change. + +If there is only one item to emit eventually, the `SingleProducer` and `SingleDelayedProducer` help work out the backpressure handling: + +```java +child.setProducer(new SingleProducer<>(child, 1)); + +// or + +SingleDelayedProducer<Integer> p = new SingleDelayedProducer<>(child); + +child.add(p); +child.setProducer(p); + +p.setValue(2); +``` + +### The queue-drain approach + +Usually, one has to serialize calls to the `onXXX` methods so only one thread at a time is in any of them. The first thought, namely using `synchronized` blocks, is forbidden. It may cause deadlocks and unnecessary thread blocking. + +Most operators, however, can use a non-blocking approach called queue-drain. It works by posting the element to be emitted (or work to be performed) onto a **queue** then atomically increments a counter. If the value before the increment was zero, it means the current thread won the right to emit the contents of the queue. Once the queue is **drained**, the counter is decremented until zero and the thread continues with other activities. + +In code: + +```java +final AtomicInteger counter = new AtomicInteger(); +final Queue<T> queue = new ConcurrentLinkedQueue<>(); + +public void onNext(T t) { + queue.offer(t); + drain(); +} + +void drain() { + if (counter.getAndIncrement() == 0) { + do { + t = queue.poll(); + child.onNext(t); + } while (counter.decrementAndGet() != 0); + } +} +``` + +Often, the when the downstream requests some amount, that should also trigger a similar drain() call: + +```java + +final AtomicLong requested = new AtomicLong(); + +@Override +public void request(long n) { + if (n > 0) { + BackpressureUtils.getAndAddRequested(requested, n); + drain(); + } +} +``` + +Many operators do more than just draining the queue and emitting its content: they have to coordinate with the downstream to emit as many items from the queue as the downstream requested. + +For example, if one writes an operator that is unbounded-in but honors the requests of the downstream, the following `drain` pattern will do the job: + +```java +// downstream's consumer +final Subscriber<? super T> child; +// temporary storage for values +final Queue<T> queue; +// mutual exclusion +final AtomicInteger counter = new AtomicInteger(); +// tracks the downstream request amount +final AtomicLong requested = new AtomicLong(); + +// no more values expected from upstream +volatile boolean done; +// the upstream error if any +Throwable error; + +void drain() { + if (counter.getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber<? super T> child = this.child; + Queue<T> queue = this.queue; + + for (;;) { + long requests = requested.get(); + long emission = 0L; + + while (emission != requests) { // don't emit more than requested + if (child.isUnsubscribed()) { + return; + } + + boolean stop = done; // order matters here! + T t = queue.poll(); + boolean empty = t == null; + + // if no more values, emit an error or completion event + if (stop && empty) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onCompleted(); + } + return; + } + // the upstream hasn't stopped yet but we don't have a value available + if (empty) { + break; + } + + child.onNext(t); + emission++; + } + + // if we are at a request boundary, a terminal event can be still emitted without requests + if (emission == requests) { + if (child.isUnsubscribed()) { + return; + } + + boolean stop = done; // order matters here! + boolean empty = queue.isEmpty(); + + // if no more values, emit an error or completion event + if (stop && empty) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onCompleted(); + } + return; + } + } + + // decrement the current request amount by the emission count + if (emission != 0L && requests != Long.MAX_VALUE) { + BackpressureUtils.produced(requested, emission); + } + + // indicate that we have performed the outstanding amount of work + missed = counter.addAndGet(-missed); + if (missed == 0) { + return; + } + // if a concurrent getAndIncrement() happened, we loop back and continue + } +} +``` + +# Creating source operators + +One creates a source operator by implementing the `OnSubscribe` interface and then calls `Observable.create` with it: + +```java +OnSubscribe<T> onSubscribe = (Subscriber<? super T> child) -> { + // logic here +}; + +Observable<T> observable = Observable.create(onSubscribe); +``` + +*Note: a common mistake when writing an operator is that one simply calls `onNext` disregarding backpressure; one should use `fromCallable` instead for synchronously (blockingly) generating a single value.* + +The `logic here` could be arbitrary complex logic. Usually, one creates a class implementing `Subscription` and `Producer`, sets it on the `child` and works out the emission pattern: + +```java +OnSubscribe<T> onSubscribe = (Subscriber<? super T> child) -> { + MySubscription mys = new MySubscription(child, otherParams); + child.add(mys); + child.setProducer(mys); + + mys.runBusinessLogic(); +}; +``` + +## Converting a callback-API to reactive + +One of the reasons custom sources are created is when one converts a classical, callback-based 'reactive' API to RxJava. In this case, one has to setup the callback on the non-RxJava source and wire up unsubscription if possible: + +```java +OnSubscribe<Data> onSubscribe = (Subscriber<? super Data> child) -> { + Callback cb = event -> { + if (event.isSuccess()) { + child.setProducer(new SingleProducer<Data>(child, event.getData())); + } else { + child.onError(event.getError()); + } + }; + + Closeable c = api.query("someinput", cb); + + child.add(Subscriptions.create(() -> Closeables.closeQuietly(c))); +}; +``` + +In this example, the `api` takes a callback and returns a `Closeable`. Our handler signals the data by setting a `SingleProducer` of it to deal with downstream backpressure. If the downstream wants to cancel a running API call, the wrap to `Subscription` will close the query. + +However, in case the callback is called more than once, one has to deal with backpressure a different way. At this level, perhaps the most easiest way is to apply `onBackpressureBuffer` or `onBackpressureDrop` on the created `Observable`: + +```java +OnSubscribe<Data> onSubscribe = (Subscriber<? super Data> child) -> { + Callback cb = event -> { + if (event.isSuccess()) { + child.onNext(event.getData()); + } else { + child.onError(event.getError()); + } + }; + + Closeable c = api.query("someinput", cb); + + child.add(Subscriptions.create(() -> Closeables.closeQuietly(c))); +}; + +Observable<T> observable = Observable.create(onSubscribe).onBackpressureBuffer(); +``` + +# Creating intermediate operators + +Writing an intermediate operator is more difficult because one may need to coordinate request amount between the upstream and downstream. + +Intermediate operators are nothing but `Subscriber`s themselves, wrapping the downstream `Subscriber` themselves, modulating the calls to `onXXX`methods and they get subscribed to the upstream's `Observable`: + +```java +Func1<T, R> mapper = ... + +Observable<T> source = ... + +OnSubscribe<R> onSubscribe = (Subscriber<? super R> child) -> { + + source.subscribe(new MapSubscriber<T, R>(child) { + @Override + public void onNext(T t) { + child.onNext(function.call(t)); + } + + // ... etc + }); + +} +``` + +Depending on whether the safety-net of the `Observable.subscribe` method is too much of an overhead, one can call `Observable.unsafeSubscribe` but then the operator has to manage and unsubscribe its own resources manually. + +This approach has a common pattern that can be factored out - at the expense of more allocation and indirection - and became the `lift` operator. + +The `lift` operator takes an `Observable.Operator<R, T>` interface implementor where `R` is the output type towards the downstream and `T` is the input type from the upstream. In our example, we can rewrite the operator as follows: + +```java + +Operator<R, T> op = child -> + return new MapSubscriber<T, R>(child) { + @Override + public void onNext(T t) { + child.onNext(function.call(t)); + } + + // ... etc + }; +} + +source.lift(op)... +``` + +The constructor of `Subscriber(Subscriber<?>)` has some caveats: it shares the underlying resource management between `child` and `MapSubscriber`. This has the unfortunate effect that when the business logic calls `MapSubscriber.unsubscribe`, it may inadvertently unsubscribe the `child`'s resources prematurely. In addition, it sets up the `Subscriber` in a way that calls to `setProducer` are forwarded to the `child` as well. + +Sometimes it is acceptable, but generally one should avoid this coupling by implementing these custom `Subscriber`s among the following pattern: + +```java +public final class MapSubscriber<T, R> extends Subscriber<T> { + final Subscriber<? super R> child; + + final Function<T, R> mapper; + + public MapSubscriber(Subscriber<? super R> child, Func1<T, R> mapper) { + // no call to super(child) ! + this.child = child; + this.mapper = mapper; + + // prevent premature requesting + this.request(0); + } + + // setup the unsubscription and request links to downstream + void init() { + child.add(this); + child.setProducer(n -> requestMore(n)); + } + + @Override + public void onNext(T t) { + try { + child.onNext(mapper.call(t)); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + // if something crashed non-fatally, unsubscribe from upstream and signal the error + unsubscribe(); + onError(ex); + } + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onCompleted() { + child.onCompleted(); + } + + void requestMore(long n) { + // deal with the downstream requests + this.request(n); + } +} + +Operator<R, T> op = child -> { + MapSubscriber<T, R> parent = new MapSubscriber<T, R>(child, mapper); + parent.init(); + return parent; +} +``` + +Some operators may not emit the received value to the `child` subscriber (such as filter). In this case, one has to call `request(1)` to ask for a replenishment because the downstream doesn't know about the dropped value and won't request itself: + +```java +// ... + + @Override + public void onNext(T t) { + try { + if (predicate.call(t)) { + child.onNext(t); + } else { + request(1); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + } + } + +// ... +``` + +When an operator maps an `onNext` emission to a terminal event then before calling the terminal event it should unsubscribe the subscriber to upstream (usually called the parent). In addition, because upstream may (legally) do something like this: + +```java +child.onNext(blah); +// no check for unsubscribed here +child.onCompleted(); +``` + +we should ensure that the operator complies with the `Observable` contract and only emits one terminal event so we use a defensive done flag: + +```java +boolean done; // = false; + +@Override +public void onError(Throwable e) { + if (done) { + return; + } + done = true; + ... +} + +@Override +public void onCompleted(Throwable e) { + if (done) { + return; + } + done = true; + ... +} +``` + +An example of this pattern is seen in `OnSubscribeMap`. + +# Further reading + +Writing operators that consume multiple source `Observable`s or produce to multiple `Subscriber`s are the most difficult one to implement. + +For inspiration, see the [blog posts](http://akarnokd.blogspot.hu/) of @akarnokd about the RxJava internals. The reader is advised to read from the very first post on and keep reading in sequence. \ No newline at end of file diff --git a/docs/Mathematical-and-Aggregate-Operators.md b/docs/Mathematical-and-Aggregate-Operators.md new file mode 100644 index 0000000000..f6bf79019e --- /dev/null +++ b/docs/Mathematical-and-Aggregate-Operators.md @@ -0,0 +1,366 @@ +This page shows operators that perform mathematical or other operations over an entire sequence of items emitted by an `Observable` or `Flowable`. Because these operations must wait for the source `Observable`/`Flowable` to complete emitting items before they can construct their own emissions (and must usually buffer these items), these operators are dangerous to use on `Observable`s and `Flowable`s that may have very long or infinite sequences. + +# Outline + +- [Mathematical Operators](#mathematical-operators) + - [`averageDouble`](#averagedouble) + - [`averageFloat`](#averagefloat) + - [`max`](#max) + - [`min`](#min) + - [`sumDouble`](#sumdouble) + - [`sumFloat`](#sumfloat) + - [`sumInt`](#sumint) + - [`sumLong`](#sumlong) +- [Standard Aggregate Operators](#standard-aggregate-operators) + - [`count`](#count) + - [`reduce`](#reduce) + - [`reduceWith`](#reducewith) + - [`collect`](#collect) + - [`collectInto`](#collectinto) + - [`toList`](#tolist) + - [`toSortedList`](#tosortedlist) + - [`toMap`](#tomap) + - [`toMultimap`](#tomultimap) + +## Mathematical Operators + +> The operators in this section are part of the [`RxJava2Extensions`](https://github.com/akarnokd/RxJava2Extensions) project. You have to add the `rxjava2-extensions` module as a dependency to your project. It can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.akarnokd%22). + +> Note that unlike the standard RxJava aggregator operators, these mathematical operators return `Observable` and `Flowable` instead of the `Single` or `Maybe`. + +*The examples below assume that the `MathObservable` and `MathFlowable` classes are imported from the `rxjava2-extensions` module:* + +```java +import hu.akarnokd.rxjava2.math.MathObservable; +import hu.akarnokd.rxjava2.math.MathFlowable; +``` + +### averageDouble + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) + +Calculates the average of `Number`s emitted by an `Observable` and emits this average as a `Double`. + +#### averageDouble example + +```java +Observable<Integer> numbers = Observable.just(1, 2, 3); +MathObservable.averageDouble(numbers).subscribe((Double avg) -> System.out.println(avg)); + +// prints 2.0 +``` + +### averageFloat + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/average.html](http://reactivex.io/documentation/operators/average.html) + +Calculates the average of `Number`s emitted by an `Observable` and emits this average as a `Float`. + +#### averageFloat example + +```java +Observable<Integer> numbers = Observable.just(1, 2, 3); +MathObservable.averageFloat(numbers).subscribe((Float avg) -> System.out.println(avg)); + +// prints 2.0 +``` + +### max + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/max.html](http://reactivex.io/documentation/operators/max.html) + +Emits the maximum value emitted by a source `Observable`. A `Comparator` can be specified that will be used to compare the elements emitted by the `Observable`. + +#### max example + +```java +Observable<Integer> numbers = Observable.just(4, 9, 5); +MathObservable.max(numbers).subscribe(System.out::println); + +// prints 9 +``` + +The following example specifies a `Comparator` to find the longest `String` in the source `Observable`: + +```java +final Observable<String> names = Observable.just("Kirk", "Spock", "Chekov", "Sulu"); +MathObservable.max(names, Comparator.comparingInt(String::length)) + .subscribe(System.out::println); + +// prints Chekov +``` + +### min + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/min.html](http://reactivex.io/documentation/operators/min.html) + +Emits the minimum value emitted by a source `Observable`. A `Comparator` can be specified that will be used to compare the elements emitted by the `Observable`. + +#### min example + +```java +Observable<Integer> numbers = Observable.just(4, 9, 5); +MathObservable.min(numbers).subscribe(System.out::println); + +// prints 4 +``` + +### sumDouble + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) + +Adds the `Double`s emitted by an `Observable` and emits this sum. + +#### sumDouble example + +```java +Observable<Double> numbers = Observable.just(1.0, 2.0, 3.0); +MathObservable.sumDouble(numbers).subscribe((Double sum) -> System.out.println(sum)); + +// prints 6.0 +``` + +### sumFloat + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) + +Adds the `Float`s emitted by an `Observable` and emits this sum. + +#### sumFloat example + +```java +Observable<Float> numbers = Observable.just(1.0F, 2.0F, 3.0F); +MathObservable.sumFloat(numbers).subscribe((Float sum) -> System.out.println(sum)); + +// prints 6.0 +``` + +### sumInt + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) + +Adds the `Integer`s emitted by an `Observable` and emits this sum. + +#### sumInt example + +```java +Observable<Integer> numbers = Observable.range(1, 100); +MathObservable.sumInt(numbers).subscribe((Integer sum) -> System.out.println(sum)); + +// prints 5050 +``` + +### sumLong + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/sum.html](http://reactivex.io/documentation/operators/sum.html) + +Adds the `Long`s emitted by an `Observable` and emits this sum. + +#### sumLong example + +```java +Observable<Long> numbers = Observable.rangeLong(1L, 100L); +MathObservable.sumLong(numbers).subscribe((Long sum) -> System.out.println(sum)); + +// prints 5050 +``` + +## Standard Aggregate Operators + +> Note that these standard aggregate operators return a `Single` or `Maybe` because the number of output items is always know to be at most one. + +### count + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/count.html](http://reactivex.io/documentation/operators/count.html) + +Counts the number of items emitted by an `Observable` and emits this count as a `Long`. + +#### count example + +```java +Observable.just(1, 2, 3).count().subscribe(System.out::println); + +// prints 3 +``` + +### reduce + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) + +Apply a function to each emitted item, sequentially, and emit only the final accumulated value. + +#### reduce example + +```java +Observable.range(1, 5) + .reduce((product, x) -> product * x) + .subscribe(System.out::println); + +// prints 120 +``` + +### reduceWith + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) + +Apply a function to each emitted item, sequentially, and emit only the final accumulated value. + + +#### reduceWith example + +```java +Observable.just(1, 2, 2, 3, 4, 4, 4, 5) + .reduceWith(TreeSet::new, (set, x) -> { + set.add(x); + return set; + }) + .subscribe(System.out::println); + +// prints [1, 2, 3, 4, 5] +``` + +### collect + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) + +Collect items emitted by the source `Observable` into a single mutable data structure and return an `Observable` that emits this structure. + +#### collect example + +```java +Observable.just("Kirk", "Spock", "Chekov", "Sulu") + .collect(() -> new StringJoiner(" \uD83D\uDD96 "), StringJoiner::add) + .map(StringJoiner::toString) + .subscribe(System.out::println); + +// prints Kirk 🖖 Spock 🖖 Chekov 🖖 Sulu +``` + +### collectInto + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/reduce.html](http://reactivex.io/documentation/operators/reduce.html) + +Collect items emitted by the source `Observable` into a single mutable data structure and return an `Observable` that emits this structure. + +#### collectInto example + +*Note: the mutable value that will collect the items (here the `StringBuilder`) will be shared between multiple subscribers.* + +```java +Observable.just('R', 'x', 'J', 'a', 'v', 'a') + .collectInto(new StringBuilder(), StringBuilder::append) + .map(StringBuilder::toString) + .subscribe(System.out::println); + +// prints RxJava +``` + +### toList + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) + +Collect all items from an `Observable` and emit them as a single `List`. + +#### toList example + +```java +Observable.just(2, 1, 3) + .toList() + .subscribe(System.out::println); + +// prints [2, 1, 3] +``` + +### toSortedList + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) + +Collect all items from an `Observable` and emit them as a single, sorted `List`. + +#### toSortedList example + +```java +Observable.just(2, 1, 3) + .toSortedList(Comparator.reverseOrder()) + .subscribe(System.out::println); + +// prints [3, 2, 1] +``` + +### toMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) + +Convert the sequence of items emitted by an `Observable` into a `Map` keyed by a specified key function. + +#### toMap example + +```java +Observable.just(1, 2, 3, 4) + .toMap((x) -> { + // defines the key in the Map + return x; + }, (x) -> { + // defines the value that is mapped to the key + return (x % 2 == 0) ? "even" : "odd"; + }) + .subscribe(System.out::println); + +// prints {1=odd, 2=even, 3=odd, 4=even} +``` + +### toMultimap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/to.html](http://reactivex.io/documentation/operators/to.html) + +Convert the sequence of items emitted by an `Observable` into a `Collection` that is also a `Map` keyed by a specified key function. + +#### toMultimap example + +```java +Observable.just(1, 2, 3, 4) + .toMultimap((x) -> { + // defines the key in the Map + return (x % 2 == 0) ? "even" : "odd"; + }, (x) -> { + // defines the value that is mapped to the key + return x; + }) + .subscribe(System.out::println); + +// prints {even=[2, 4], odd=[1, 3]} +``` diff --git a/docs/Observable-Utility-Operators.md b/docs/Observable-Utility-Operators.md new file mode 100644 index 0000000000..b23c871894 --- /dev/null +++ b/docs/Observable-Utility-Operators.md @@ -0,0 +1,28 @@ +This page lists various utility operators for working with Observables. + +* [**`materialize( )`**](http://reactivex.io/documentation/operators/materialize-dematerialize.html) — convert an Observable into a list of Notifications +* [**`dematerialize( )`**](http://reactivex.io/documentation/operators/materialize-dematerialize.html) — convert a materialized Observable back into its non-materialized form +* [**`timestamp( )`**](http://reactivex.io/documentation/operators/timestamp.html) — attach a timestamp to every item emitted by an Observable +* [**`serialize( )`**](http://reactivex.io/documentation/operators/serialize.html) — force an Observable to make serialized calls and to be well-behaved +* [**`cache( )`**](http://reactivex.io/documentation/operators/replay.html) — remember the sequence of items emitted by the Observable and emit the same sequence to future Subscribers +* [**`observeOn( )`**](http://reactivex.io/documentation/operators/observeon.html) — specify on which Scheduler a Subscriber should observe the Observable +* [**`subscribeOn( )`**](http://reactivex.io/documentation/operators/subscribeon.html) — specify which Scheduler an Observable should use when its subscription is invoked +* [**`doOnEach( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take whenever an Observable emits an item +* [**`doOnNext( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to call just before the Observable passes an `onNext` event along to its downstream +* [**`doAfterNext( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to call after the Observable has passed an `onNext` event along to its downstream +* [**`doOnCompleted( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take when an Observable completes successfully +* [**`doOnError( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take when an Observable completes with an error +* [**`doOnTerminate( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to call just before an Observable terminates, either successfully or with an error +* [**`doAfterTerminate( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to call just after an Observable terminated, either successfully or with an error +* [**`doOnSubscribe( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take when an observer subscribes to an Observable +* *1.x* [**`doOnUnsubscribe( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take when an observer unsubscribes from an Observable +* [**`finallyDo( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to take when an Observable completes +* [**`doFinally( )`**](http://reactivex.io/documentation/operators/do.html) — register an action to call when an Observable terminates or it gets disposed +* [**`delay( )`**](http://reactivex.io/documentation/operators/delay.html) — shift the emissions from an Observable forward in time by a specified amount +* [**`delaySubscription( )`**](http://reactivex.io/documentation/operators/delay.html) — hold an Subscriber's subscription request for a specified amount of time before passing it on to the source Observable +* [**`timeInterval( )`**](http://reactivex.io/documentation/operators/timeinterval.html) — emit the time lapsed between consecutive emissions of a source Observable +* [**`using( )`**](http://reactivex.io/documentation/operators/using.html) — create a disposable resource that has the same lifespan as an Observable +* [**`single( )`**](http://reactivex.io/documentation/operators/first.html) — if the Observable completes after emitting a single item, return that item, otherwise throw an exception +* [**`singleOrDefault( )`**](http://reactivex.io/documentation/operators/first.html) — if the Observable completes after emitting a single item, return that item, otherwise return a default item +* [**`repeat( )`**](http://reactivex.io/documentation/operators/repeat.html) — create an Observable that emits a particular item or sequence of items repeatedly +* [**`repeatWhen( )`**](http://reactivex.io/documentation/operators/repeat.html) — create an Observable that emits a particular item or sequence of items repeatedly, depending on the emissions of a second Observable \ No newline at end of file diff --git a/docs/Observable.md b/docs/Observable.md new file mode 100644 index 0000000000..fec5467908 --- /dev/null +++ b/docs/Observable.md @@ -0,0 +1,3 @@ +In RxJava an object that implements the _Observer_ interface _subscribes_ to an object of the _Observable_ class. Then that subscriber reacts to whatever item or sequence of items the Observable object _emits_. This pattern facilitates concurrent operations because it does not need to block while waiting for the Observable to emit objects, but instead it creates a sentry in the form of a subscriber that stands ready to react appropriately at whatever future time the Observable does so. + +For information about the Observable class, see [the Observable documentation page at ReactiveX.io](http://reactivex.io/documentation/observable.html). \ No newline at end of file diff --git a/docs/Parallel-flows.md b/docs/Parallel-flows.md new file mode 100644 index 0000000000..8cd0e9a8c9 --- /dev/null +++ b/docs/Parallel-flows.md @@ -0,0 +1,33 @@ +# Introduction + +Version 2.0.5 introduced the `ParallelFlowable` API that allows parallel execution of a few select operators such as `map`, `filter`, `concatMap`, `flatMap`, `collect`, `reduce` and so on. Note that is a **parallel mode** for `Flowable` (a sub-domain specific language) instead of a new reactive base type. + +Consequently, several typical operators such as `take`, `skip` and many others are not available and there is no `ParallelObservable` because **backpressure** is essential in not flooding the internal queues of the parallel operators as by expectation, we want to go parallel because the processing of the data is slow on one thread. + +The easiest way of entering the parallel world is by using `Flowable.parallel`: + +```java +ParallelFlowable<Integer> source = Flowable.range(1, 1000).parallel(); +``` + +By default, the parallelism level is set to the number of available CPUs (`Runtime.getRuntime().availableProcessors()`) and the prefetch amount from the sequential source is set to `Flowable.bufferSize()` (128). Both can be specified via overloads of `parallel()`. + +`ParallelFlowable` follows the same principles of parametric asynchrony as `Flowable` does, therefore, `parallel()` on itself doesn't introduce the asynchronous consumption of the sequential source but only prepares the parallel flow; the asynchrony is defined via the `runOn(Scheduler)` operator. + +```java +ParallelFlowable<Integer> psource = source.runOn(Schedulers.io()); +``` + +The parallelism level (`ParallelFlowable.parallelism()`) doesn't have to match the parallelism level of the `Scheduler`. The `runOn` operator will use as many `Scheduler.Worker` instances as defined by the parallelized source. This allows `ParallelFlowable` to work for CPU intensive tasks via `Schedulers.computation()`, blocking/IO bound tasks through `Schedulers.io()` and unit testing via `TestScheduler`. You can specify the prefetch amount on `runOn` as well. + +Once the necessary parallel operations have been applied, you can return to the sequential `Flowable` via the `ParallelFlowable.sequential()` operator. + +```java +Flowable<Integer> result = psource.filter(v -> v % 3 == 0).map(v -> v * v).sequential(); +``` + +Note that `sequential` doesn't guarantee any ordering between values flowing through the parallel operators. + +# Parallel operators + +TBD \ No newline at end of file diff --git a/docs/Phantom-Operators.md b/docs/Phantom-Operators.md new file mode 100644 index 0000000000..60da4a1a40 --- /dev/null +++ b/docs/Phantom-Operators.md @@ -0,0 +1,166 @@ +These operators have been proposed but are not part of the 1.0 release of RxJava. + +* [**`chunkify( )`**](Phantom-Operators#chunkify) — returns an iterable that periodically returns a list of items emitted by the source Observable since the last list +* [**`fromFuture( )`**](Phantom-Operators#fromfuture) — convert a Future into an Observable, but do not attempt to get the Future's value until a Subscriber subscribes +* [**`forEachFuture( )`**](Phantom-Operators#foreachfuture) — create a futureTask that will invoke a specified function on each item emitted by an Observable +* [**`forIterable( )`**](Phantom-Operators#foriterable) — apply a function to the elements of an Iterable to create Observables which are then concatenated +* [**`fromCancellableFuture( )`, `startCancellableFuture( )`, and `deferCancellableFuture( )`**](Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture-) — versions of Future-to-Observable converters that monitor the subscription status of the Observable to determine whether to halt work on the Future +* [**`generate( )` and `generateAbsoluteTime( )`**](Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing +* [**`groupByUntil( )`**](Phantom-Operators#groupbyuntil) — a variant of the `groupBy` operator that closes any open `GroupedObservable` upon a signal from another Observable +* [**`multicast( )`**](Phantom-Operators#multicast) — represents an Observable as a Connectable Observable +* [**`onErrorFlatMap( )`**](Phantom-Operators#onerrorflatmap) — instructs an Observable to emit a sequence of items whenever it encounters an error +* [**`parallel( )`**](Phantom-Operators#parallel) — split the work done on the emissions from an Observable into multiple Observables each operating on its own parallel thread +* [**`parallelMerge( )`**](Phantom-Operators#parallelmerge) — combine multiple Observables into a smaller number of Observables, to facilitate parallelism +* [**`pivot( )`**](Phantom-Operators#pivot) — combine multiple sets of grouped observables so that they are arranged primarily by group rather than by set +* [**`publishLast( )`**](Phantom-Operators#publishlast) — represent an Observable as a Connectable Observable that emits only the last item emitted by the source Observable + + +*** + +## chunkify( ) +#### returns an iterable that periodically returns a list of items emitted by the source Observable since the last list +<img src="/Netflix/RxJava/wiki/images/rx-operators/B.chunkify.png" width="640" height="490" /> + +The `chunkify( )` operator represents a blocking observable as an Iterable, that, each time you iterate over it, returns a list of items emitted by the source Observable since the previous iteration. These lists may be empty if there have been no such items emitted. + +*** + +## fromFuture( ) +#### convert a Future into an Observable, but do not attempt to get the Future's value until a Subscriber subscribes +<img src="/Netflix/RxJava/wiki/images/rx-operators/fromFuture.png" width="640" height="335" /> + +The `fromFuture( )` method also converts a Future into an Observable, but it obtains this Future indirectly, by means of a function you provide. It creates the Observable immediately, but waits to call the function and to obtain the Future until a Subscriber subscribes to it. + +*** + +## forEachFuture( ) +#### create a futureTask that will invoke a specified function on each item emitted by an Observable +<img src="/Netflix/RxJava/wiki/images/rx-operators/B.forEachFuture.png" width="640" height="375" /> + +The `forEachFuture( )` returns a `FutureTask` for each item emitted by the source Observable (or each item and each notification) that, when executed, will apply a function you specify to each such item (or item and notification). + +*** + +## forIterable( ) +#### apply a function to the elements of an Iterable to create Observables which are then concatenated +<img src="/Netflix/RxJava/wiki/images/rx-operators/forIterable.png" width="640" height="310" /> + +`forIterable( )` is similar to `from(Iterable )` but instead of the resulting Observable emitting the elements of the Iterable as its own emitted items, it applies a specified function to each of these elements to generate one Observable per element, and then concatenates the emissions of these Observables to be its own sequence of emitted items. + +*** + +## fromCancellableFuture( ), startCancellableFuture( ), and deferCancellableFuture( ) +#### versions of Future-to-Observable converters that monitor the subscription status of the Observable to determine whether to halt work on the Future + +If the a subscriber to the Observable that results when a Future is converted to an Observable later unsubscribes from that Observable, it can be useful to have the ability to stop attempting to retrieve items from the Future. The "cancellable" Future enables you do do this. These three methods will return Observables that, when unsubscribed to, will also "unsubscribe" from the underlying Futures. + +*** + +## generate( ) and generateAbsoluteTime( ) +#### create an Observable that emits a sequence of items as generated by a function of your choosing +<img src="/Netflix/RxJava/wiki/images/rx-operators/generate.png" width="640" height="315" /> + +The basic form of `generate( )` takes four parameters. These are `initialState` and three functions: `iterate( )`, `condition( )`, and `resultSelector( )`. `generate( )` uses these four parameters to generate an Observable sequence, which is its return value. It does so in the following way. + +`generate( )` creates each emission from the sequence by applying the `resultSelector( )` function to the current _state_ and emitting the resulting item. The first state, which determines the first emitted item, is `initialState`. `generate( )` determines each subsequent state by applying `iterate( )` to the current state. Before emitting an item, `generate( )` tests the result of `condition( )` applied to the current state. If the result of this test is `false`, instead of calling `resultSelector( )` and emitting the resulting value, `generate( )` terminates the sequence and stops iterating the state. + +There are also versions of `generate( )` that allow you to do the work of generating the sequence on a particular `Scheduler` and that allow you to set the time interval between emissions by applying a function to the current state. The `generateAbsoluteTime( )` allows you to control the time at which an item is emitted by applying a function to the state to get an absolute system clock time (rather than an interval from the previous emission). + +<img src="/Netflix/RxJava/wiki/images/rx-operators/generateAbsoluteTime.png" width="640" height="330" /> + +#### see also: +* <a href="http://www.introtorx.com/Content/v1.0.10621.0/04_CreatingObservableSequences.html#ObservableGenerate">Introduction to Rx: Generate</a> +* Linq: <a href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.generate.aspx">`Generate`</a> +* RxJS: <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservablegenerateinitialstate-condition-iterate-resultselector-scheduler">`generate`</a>, <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservablegeneratewithabsolutetimeinitialstate-condition-iterate-resultselector-timeselector-scheduler">`generateWithAbsoluteTime`</a>, and <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservablegeneratewithrelativetimeinitialstate-condition-iterate-resultselector-timeselector-scheduler">`generateWithRelativeTime`</a> + +*** +## groupByUntil( ) +#### a variant of the `groupBy` operator that closes any open `GroupedObservable` upon a signal from another Observable + +This version of `groupBy` adds another parameter: an Observable that emits duration markers. When a duration marker is emitted by this Observable, any grouped Observables that have been opened are closed, and `groupByUntil( )` will create new grouped Observables for any subsequent emissions by the source Observable. + +<img src="/ReactiveX/RxJava/wiki/images/rx-operators/groupByUntil.png" width="640" height="375" />​ + +Another variety of `groupByUntil( )` limits the number of groups that can be active at any particular time. If an item is emitted by the source Observable that would cause the number of groups to exceed this maximum, before the new group is emitted, one of the existing groups is closed (that is, the Observable it represents terminates by calling its Subscribers' `onCompleted` methods and then expires). + +*** + +## multicast( ) +#### represents an Observable as a Connectable Observable +To represent an Observable as a Connectable Observable, use the `multicast( )` method. + +#### see also: +* javadoc: <a href="http://reactivex.io/RxJava/javadoc/rx/Observable.html#multicast(rx.functions.Func0)">`multicast(subjectFactory)`</a> +* javadoc: <a href="http://reactivex.io/RxJava/javadoc/rx/Observable.html#multicast(rx.functions.Func0, rx.functions.Func1)">`multicast(subjectFactory, selector)`</a> +* RxJS: <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypemulticastsubject--subjectselector-selector">`multicast`</a> +* Linq: <a href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.multicast.aspx">`Multicast`</a> +* <a href="http://www.introtorx.com/Content/v1.0.10621.0/14_HotAndColdObservables.html#PublishAndConnect">Introduction to Rx: Publish and Connect</a> +* <a href="http://www.introtorx.com/Content/v1.0.10621.0/14_HotAndColdObservables.html#Multicast">Introduction to Rx: Multicast</a> + +*** + +## onErrorFlatMap( ) +#### instructs an Observable to emit a sequence of items whenever it encounters an error +<img src="/Netflix/RxJava/wiki/images/rx-operators/onErrorFlatMap.png" width="640" height="310" />​ + +The `onErrorFlatMap( )` method is similar to `onErrorResumeNext( )` except that it does not assume the source Observable will correctly terminate when it issues an error. Because of this, after emitting its backup sequence of items, `onErrorFlatMap( )` relinquishes control of the emitted sequence back to the source Observable. If that Observable again issues an error, `onErrorFlatMap( )` will again emit its backup sequence. + +The backup sequence is an Observable that is returned from a function that you pass to `onErrorFlatMap( )`. This function takes the Throwable issued by the source Observable as its argument, and so you can customize the sequence based on the nature of the Throwable. + +Because `onErrorFlatMap( )` is designed to work with pathological source Observables that do not terminate after issuing an error, it is mostly useful in debugging/testing scenarios. + +Note that you should apply `onErrorFlatMap( )` directly to the pathological source Observable, and not to that Observable after it has been modified by additional operators, as such operators may effectively renormalize the source Observable by unsubscribing from it immediately after it issues an error. Below, for example, is an illustration showing how `onErrorFlatMap( )` will respond to two error-generating Observables that have been merged by the `merge( )` operator. Note that it will *not* react to both errors generated by both Observables, but only to the single error passed along by `merge( )`: + +<img src="/Netflix/RxJava/wiki/images/rx-operators/onErrorFlatMap.withMerge.png" width="640" height="630" />​ + +*** + +## parallel( ) +#### split the work done on the emissions from an Observable into multiple Observables each operating on its own parallel thread +<img src="/ReactiveX/RxJava/wiki/images/rx-operators/parallel.png" width="640" height="475" />​ + +The `parallel( )` method splits an Observable into as many Observables as there are available processors, and does work in parallel on each of these Observables. `parallel( )` then merges the results of these parallel computations back into a single, well-behaved Observable sequence. + +For the simple “run things in parallel” use case, you can instead use something like this: +```java +streamOfItems.flatMap(item -> { + itemToObservable(item).subscribeOn(Schedulers.io()); +}); +``` +Kick off your work for each item inside [`flatMap`](Transforming-Observables#flatmap-concatmap-and-flatmapiterable) using [`subscribeOn`](Observable-Utility-Operators#subscribeon) to make it asynchronous, or by using a function that already makes asychronous calls. + +#### see also: +* <a href="http://www.grahamlea.com/2014/07/rxjava-threading-examples/">RxJava Threading Examples</a> by Graham Lea + +*** + +## parallelMerge( ) +#### combine multiple Observables into a smaller number of Observables, to facilitate parallelism +<img src="/ReactiveX/RxJava/wiki/images/rx-operators/parallelMerge.png" width="640" height="535" />​ + +Use the `parallelMerge( )` method to take an Observable that emits a large number of Observables and to reduce it to an Observable that emits a particular, smaller number of Observables that emit the same set of items as the original larger set of Observables: for instance a number of Observables that matches the number of parallel processes that you want to use when processing the emissions from the complete set of Observables. + +*** + +## pivot( ) +#### combine multiple sets of grouped observables so that they are arranged primarily by group rather than by set +<img src="/Netflix/RxJava/wiki/images/rx-operators/pivot.png" width="640" height="580" />​ + +If you combine multiple sets of grouped observables, such as those created by [`groupBy( )` and `groupByUntil( )`](Transforming-Observables#wiki-groupby-and-groupbyuntil), then even if those grouped observables have been grouped by a similar differentiation function, the resulting grouping will be primarily based on which set the observable came from, not on which group the observable belonged to. + +An example may make this clearer. Imagine you use `groupBy( )` to group the emissions of an Observable (Observable1) that emits integers into two grouped observables, one emitting the even integers and the other emitting the odd integers. You then repeat this process on a second Observable (Observable2) that emits another set of integers. You hope then to combine the sets of grouped observables emitted by each of these into a single grouped Observable by means of a operator like `from(Observable1, Observable2)`. + +The result will be a grouped observable that emits two groups: the grouped observable resulting from transforming Observable1, and the grouped observable resulting from transforming Observable2. Each of those grouped observables emit observables that in turn emit the odds and evens from the source observables. You can use `pivot( )` to change this around: by applying `pivot( )` to this grouped observable it will transform into one that emits two different groups: the odds group and the evens group, with each of these groups emitting a separate observable corresponding to which source observable its set of integers came from. Here is an illustration: + +<img src="/Netflix/RxJava/wiki/images/rx-operators/pivot.ex.png" width="640" height="1140" />​ + +*** + +## publishLast( ) +#### represent an Observable as a Connectable Observable that emits only the last item emitted by the source Observable +<img src="/ReactiveX/RxJava/wiki/images/rx-operators/publishLast.png" width="640" height="310" /> + +#### see also: +* RxJS: <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypepublishlatestselector">`publishLast`</a> +* Linq: <a href="http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.publishlast.aspx">`PublishLast`</a> +* <a href="http://www.introtorx.com/Content/v1.0.10621.0/14_HotAndColdObservables.html#PublishLast">Introduction to Rx: PublishLast</a> \ No newline at end of file diff --git a/docs/Plugins.md b/docs/Plugins.md new file mode 100644 index 0000000000..38dfcafd12 --- /dev/null +++ b/docs/Plugins.md @@ -0,0 +1,171 @@ +Plugins allow you to modify the default behavior of RxJava in several respects: + +* by changing the set of default computation, i/o, and new thread Schedulers +* by registering a handler for extraordinary errors that RxJava may encounter +* by registering functions that can take note of the occurrence of several regular RxJava activities + +As of 1.1.7 the regular `RxJavaPlugins` and the other hook classes have been deprecated in favor of `RxJavaHooks`. + +# RxJavaHooks + +The new `RxJavaHooks` allows you to hook into the lifecycle of the `Observable`, `Single` and `Completable` types, the `Scheduler`s returned by `Schedulers` and offers a catch-all for undeliverable errors. + +You can now change these hooks at runtime and there is no need to prepare hooks via system parameters anymore. Since users may still rely on the old hooking system, RxJavaHooks delegates to those old hooks by default. + +The `RxJavaHooks` has setters and getters of the various hook types: + +| Hook | Description | +|------|-------------| +| onError : `Action1<Throwable>` | Sets the catch-all callback | +| onObservableCreate : `Func1<Observable.OnSubscribe, Observable.OnSubscribe>` | Called when operators and sources are instantiated on `Observable` | +| onObservableStart : `Func2<Observable, Observable.OnSubscribe, Observable.OnSubscribe>` | Called before subscribing to an `Observable` actually happens | +| onObservableSubscribeError : `Func1<Throwable, Throwable>` | Called when subscribing to an `Observable` fails | +| onObservableReturn : `Func1<Subscription, Subscription>` | Called when the subscribing to an `Observable` succeeds and before returning the `Subscription` handler for it | +| onObservableLift : `Func1<Observable.Operator, Observable.Operator>` | Called when the operator `lift` is used with `Observable` | +| onSingleCreate : `Func1<Single.OnSubscribe, Single.OnSubscribe>` | Called when operators and sources are instantiated on `Single` | +| onSingleStart : `Func2<Single, Observable.OnSubscribe, Observable.OnSubscribe>` | Called before subscribing to a `Single` actually happens | +| onSingleSubscribeError : `Func1<Throwable, Throwable>` | Called when subscribing to a `Single` fails | +| onSingleReturn : `Func1<Subscription, Subscription>` | Called when the subscribing to a `Single` succeeds and before returning the `Subscription` handler for it | +| onSingleLift : `Func1<Observable.Operator, Observable.Operator>` | Called when the operator `lift` is used (note: `Observable.Operator` is deliberate here) | +| onCompletableCreate : `Func1<Completable.OnSubscribe, Completable.OnSubscribe>` | Called when operators and sources are instantiated on `Completable` | +| onCompletableStart : `Func2<Completable, Completable.OnSubscribe, Completable.OnSubscribe>` | Called before subscribing to a `Completable` actually happens | +| onCompletableSubscribeError : `Func1<Throwable, Throwable>` | Called when subscribing to a `Completable` fails | +| onCompletableLift : `Func1<Completable.Operator, Completable.Operator>` | Called when the operator `lift` is used with `Completable` | +| onComputationScheduler : `Func1<Scheduler, Scheduler>` | Called when using `Schedulers.computation()` | +| onIOScheduler : `Func1<Scheduler, Scheduler>` | Called when using `Schedulers.io()` | +| onNewThreadScheduler : `Func1<Scheduler, Scheduler>` | Called when using `Schedulers.newThread()` | +| onScheduleAction : `Func1<Action0, Action0>` | Called when a task gets scheduled in any of the `Scheduler`s | +| onGenericScheduledExecutorService : `Func0<ScheduledExecutorService>` | that should return single-threaded executors to support background timed tasks of RxJava itself | + +Reading and changing these hooks is thread-safe. + +You can also clear all hooks via `clear()` or reset to the default behavior (of delegating to the old RxJavaPlugins system) via `reset()`. + +Example: + +```java +RxJavaHooks.setOnObservableCreate(o -> { + System.out.println("Creating " + o.getClass()); + return o; +}); +try { + Observable.range(1, 10) + .map(v -> v * 2) + .filter(v -> v % 4 == 0) + .subscribe(System.out::println); +} finally { + RxJavaHooks.reset(); +} +``` + +In addition, the `RxJavaHooks` offers the so-called assembly tracking feature. This shims a custom `Observable`, `Single` and `Completable` into their chains which captures the current stacktrace when those operators were instantiated (assembly-time). Whenever an error is signalled via onError, these middle components attach this assembly-time stacktraces as last causes of that exception. This may help locating the problematic sequence in a codebase where there are too many similar flows and the plain exception itself doesn't tell which one failed in your codebase. + +Example: + +```java +RxJavaHooks.enableAssemblyTracking(); +try { + Observable.empty().single() + .subscribe(System.out::println, Throwable::printStackTrace); +} finally { + RxJavaHooks.resetAssemblyTracking(); +} +``` + +This will print something like this: + +``` +java.lang.NoSuchElementException +at rx.internal.operators.OnSubscribeSingle(OnSubscribeSingle.java:57) +... +Assembly trace: +at com.example.TrackingExample(TrackingExample:10) +``` + +The stacktrace string is also available in a field to support debugging and discovering the status of various operators in a running chain. + +The stacktrace is filtered by removing irrelevant entries such as Thread entry points, unit test runners and the entries of the tracking system itself to reduce noise. + +# RxJavaSchedulersHook + +**Deprecated** + +This plugin allows you to override the default computation, i/o, and new thread Schedulers with Schedulers of your choosing. To do this, extend the class `RxJavaSchedulersHook` and override these methods: + +* `Scheduler getComputationScheduler( )` +* `Scheduler getIOScheduler( )` +* `Scheduler getNewThreadScheduler( )` +* `Action0 onSchedule(action)` + +Then follow these steps: + +1. Create an object of the new `RxJavaDefaultSchedulers` subclass you have implemented. +1. Obtain the global `RxJavaPlugins` instance via `RxJavaPlugins.getInstance( )`. +1. Pass your default schedulers object to the `registerSchedulersHook( )` method of that instance. + +When you do this, RxJava will begin to use the Schedulers returned by your methods rather than its built-in defaults. + +# RxJavaErrorHandler + +**Deprecated** + +This plugin allows you to register a function that will handle errors that are passed to `SafeSubscriber.onError(Throwable)`. (`SafeSubscriber` is used for wrapping the incoming `Subscriber` when one calls `subscribe()`). To do this, extend the class `RxJavaErrorHandler` and override this method: + +* `void handleError(Throwable e)` + +Then follow these steps: + +1. Create an object of the new `RxJavaErrorHandler` subclass you have implemented. +1. Obtain the global `RxJavaPlugins` instance via `RxJavaPlugins.getInstance( )`. +1. Pass your error handler object to the `registerErrorHandler( )` method of that instance. + +When you do this, RxJava will begin to use your error handler to field errors that are passed to `SafeSubscriber.onError(Throwable)`. + +For example, this will call the hook: + +```java +RxJavaPlugins.getInstance().reset(); + +RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { + @Override + public void handleError(Throwable e) { + e.printStackTrace(); + } +}); + +Observable.error(new IOException()) +.subscribe(System.out::println, e -> { }); +``` + +however, this call and chained operators in general won't trigger it in each stage: + +```java +Observable.error(new IOException()) +.map(v -> "" + v) +.unsafeSubscribe(System.out::println, e -> { }); +``` + +# RxJavaObservableExecutionHook + +**Deprecated** + +This plugin allows you to register functions that RxJava will call upon certain regular RxJava activities, for instance for logging or metrics-collection purposes. To do this, extend the class `RxJavaObservableExecutionHook` and override any or all of these methods: + +<table><thead> + <tr><th>method</th><th>when invoked</th></tr> + </thead><tbody> + <tr><td><tt>onCreate( )</tt></td><td>during <tt>Observable.create( )</tt></td></tr> + <tr><td><tt>onSubscribeStart( )</tt></td><td>immediately before <tt>Observable.subscribe( )</tt></td></tr> + <tr><td><tt>onSubscribeReturn( )</tt></td><td>immediately after <tt>Observable.subscribe( )</tt></td></tr> + <tr><td><tt>onSubscribeError( )</tt></td><td>upon a failed execution of <tt>Observable.subscribe( )</tt></td></tr> + <tr><td><tt>onLift( )</tt></td><td>during <tt>Observable.lift( )</tt></td></tr> + </tbody> +</table> + +Then follow these steps: + +1. Create an object of the new `RxJavaObservableExecutionHook` subclass you have implemented. +1. Obtain the global `RxJavaPlugins` instance via `RxJavaPlugins.getInstance( )`. +1. Pass your execution hooks object to the `registerObservableExecutionHook( )` method of that instance. + +When you do this, RxJava will begin to call your functions when it encounters the specific conditions they were designed to take note of. \ No newline at end of file diff --git a/docs/Problem-Solving-Examples-in-RxJava.md b/docs/Problem-Solving-Examples-in-RxJava.md new file mode 100644 index 0000000000..6b848ae693 --- /dev/null +++ b/docs/Problem-Solving-Examples-in-RxJava.md @@ -0,0 +1,130 @@ +This page will present some elementary RxJava puzzles and walk through some solutions as a way of introducing you to some of the RxJava operators. + +# Project Euler problem #1 + +There used to be a site called "Project Euler" that presented a series of mathematical computing conundrums (some fairly easy, others quite baffling) and challenged people to solve them. The first one was a sort of warm-up exercise: + +> If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000. + +There are several ways we could go about this with RxJava. We might, for instance, begin by going through all of the natural numbers below 1000 with [`range`](Creating-Observables#range) and then [`filter`](Filtering-Observables#filter) out those that are not a multiple either of 3 or of 5: +### Java +```java +Observable<Integer> threesAndFives = Observable.range(1, 999).filter(e -> e % 3 == 0 || e % 5 == 0); +``` +### Groovy +````groovy +def threesAndFives = Observable.range(1,999).filter({ !((it % 3) && (it % 5)) }); +```` +Or, we could generate two Observable sequences, one containing the multiples of three and the other containing the multiples of five (by [`map`](https://github.com/Netflix/RxJava/wiki/Transforming-Observables#map)ping each value onto its appropriate multiple), making sure to only generating new multiples while they are less than 1000 (the [`takeWhile`](Conditional-and-Boolean-Operators#takewhile-and-takewhilewithindex) operator will help here), and then [`merge`](Combining-Observables#merge) these sets: +### Java +```java +Observable<Integer> threes = Observable.range(1, 999).map(e -> e * 3).takeWhile(e -> e < 1000); +Observable<Integer> fives = Observable.range(1, 999).map(e -> e * 5).takeWhile(e -> e < 1000); +Observable<Integer> threesAndFives = Observable.merge(threes, fives).distinct(); +``` +### Groovy +````groovy +def threes = Observable.range(1,999).map({it*3}).takeWhile({it<1000}); +def fives = Observable.range(1,999).map({it*5}).takeWhile({it<1000}); +def threesAndFives = Observable.merge(threes, fives).distinct(); +```` +Don't forget the [`distinct`](Filtering-Observables#distinct) operator here, otherwise merge will duplicate numbers like 15 that are multiples of both 5 and 3. + +Next, we want to sum up the numbers in the resulting sequence. If you have installed the optional `rxjava-math` module, this is elementary: just use an operator like [`sumInteger` or `sumLong`](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) on the `threesAndFives` Observable. But what if you don't have this module? How could you use standard RxJava operators to sum up a sequence and emit that sum? + +There are a number of operators that reduce a sequence emitted by a source Observable to a single value emitted by the resulting Observable. Most of the ones that are not in the `rxjava-math` module emit boolean evaluations of the sequence; we want something that can emit a number. The [`reduce`](Mathematical-and-Aggregate-Operators#reduce) operator will do the job: +### Java +```java +Single<Integer> summer = threesAndFives.reduce(0, (a, b) -> a + b); +``` +### Groovy +````groovy +def summer = threesAndFives.reduce(0, { a, b -> a+b }); +```` +Here is how `reduce` gets the job done. It starts with 0 as a seed. Then, with each item that `threesAndFives` emits, it calls the closure `{ a, b -> a+b }`, passing it the current seed value as `a` and the emission as `b`. The closure adds these together and returns that sum, and `reduce` uses this returned value to overwrite its seed. When `threesAndFives` completes, `reduce` emits the final value returned from the closure as its sole emission: +<table> + <thead> + <tr><th>iteration</th><th>seed</th><th>emission</th><th>reduce</th></tr> + </thead> + <tbody> + <tr><td>1</td><td>0</td><td>3</td><td>3</td></tr> + <tr><td>2</td><td>3</td><td>5</td><td>8</td></tr> + <tr><td>3</td><td>8</td><td>6</td><td>14</td></tr> + <tr><td colspan="4"><center>…</center></td></tr> + <tr><td>466</td><td>232169</td><td>999</td><td>233168</td></tr> + </tbody> +</table> +Finally, we want to see the result. This means we must [subscribe](Observable#onnext-oncompleted-and-onerror) to the Observable we have constructed: + +### Java +```java +summer.subscribe(System.out::print); +``` +### Groovy +````groovy +summer.subscribe({println(it);}); +```` + +# Generate the Fibonacci Sequence + +How could you create an Observable that emits [the Fibonacci sequence](http://en.wikipedia.org/wiki/Fibonacci_number)? + +The most direct way would be to use the [`create`](Creating-Observables#wiki-create) operator to make an Observable "from scratch," and then use a traditional loop within the closure you pass to that operator to generate the sequence. Something like this: +### Java +```java +Observable<Integer> fibonacci = Observable.create(emitter -> { + int f1 = 0, f2 = 1, f = 1; + while (!emitter.isDisposed()) { + emitter.onNext(f); + f = f1 + f2; + f1 = f2; + f2 = f; + } +}); +``` +### Groovy +````groovy +def fibonacci = Observable.create({ emitter -> + def f1=0, f2=1, f=1; + while(!emitter.isDisposed()) { + emitter.onNext(f); + f = f1+f2; + f1 = f2; + f2 = f; + }; +}); +```` +But this is a little too much like ordinary linear programming. Is there some way we can instead create this sequence by composing together existing Observable operators? + +Here's an option that does this: +### Java +```java +Observable<Integer> fibonacci = + Observable.fromArray(0) + .repeat() + .scan(new int[]{0, 1}, (a, b) -> new int[]{a[1], a[0] + a[1]}) + .map(a -> a[1]); +``` +### Groovy +````groovy +def fibonacci = Observable.from(0).repeat().scan([0,1], { a,b -> [a[1], a[0]+a[1]] }).map({it[1]}); +```` +It's a little [janky](http://www.urbandictionary.com/define.php?term=janky). Let's walk through it: + +The `Observable.from(0).repeat()` creates an Observable that just emits a series of zeroes. This just serves as grist for the mill to keep [`scan`](Transforming-Observables#scan) operating. The way `scan` usually behaves is that it operates on the emissions from an Observable, one at a time, accumulating the result of operations on each emission in some sort of register, which it emits as its own emissions. The way we're using it here, it ignores the emissions from the source Observable entirely, and simply uses these emissions as an excuse to transform and emit its register. That register gets `[0,1]` as a seed, and with each iteration changes the register from `[a,b]` to `[b,a+b]` and then emits this register. + +This has the effect of emitting the following sequence of items: `[0,1], [1,1], [1,2], [2,3], [3,5], [5,8]...` + +The second item in this array describes the Fibonacci sequence. We can use `map` to reduce the sequence to just that item. + +To print out a portion of this sequence (using either method), you would use code like the following: +### Java +```java +fibonacci.take(15).subscribe(System.out::println); +``` +### Groovy +````groovy +fibonnaci.take(15).subscribe({println(it)})]; +```` + +Is there a less-janky way to do this? The [`generate`](https://github.com/Netflix/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime) operator would avoid the silliness of creating an Observable that does nothing but turn the crank of `seed`, but this operator is not yet part of RxJava. Perhaps you can think of a more elegant solution? \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..474c8edc77 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,24 @@ +RxJava is a Java VM implementation of [ReactiveX (Reactive Extensions)](https://reactivex.io): a library for composing asynchronous and event-based programs by using observable sequences. + +For more information about ReactiveX, see the [Introduction to ReactiveX](http://reactivex.io/intro.html) page. + +### RxJava is Lightweight + +RxJava tries to be very lightweight. It is implemented as a single JAR that is focused on just the Observable abstraction and related higher-order functions. + +### RxJava is a Polyglot Implementation + +RxJava supports Java 6 or higher and JVM-based languages such as [Groovy](https://github.com/ReactiveX/RxGroovy), [Clojure](https://github.com/ReactiveX/RxClojure), [JRuby](https://github.com/ReactiveX/RxJRuby), [Kotlin](https://github.com/ReactiveX/RxKotlin) and [Scala](https://github.com/ReactiveX/RxScala). + +RxJava is meant for a more polyglot environment than just Java/Scala, and it is being designed to respect the idioms of each JVM-based language. (<a href="https://github.com/Netflix/RxJava/pull/304">This is something we’re still working on.</a>) + +### RxJava Libraries + +The following external libraries can work with RxJava: + +* [Hystrix](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Reactive-Execution) latency and fault tolerance bulkheading library. +* [Camel RX](http://camel.apache.org/rx.html) provides an easy way to reuse any of the [Apache Camel components, protocols, transports and data formats](http://camel.apache.org/components.html) with the RxJava API +* [rxjava-http-tail](https://github.com/myfreeweb/rxjava-http-tail) allows you to follow logs over HTTP, like `tail -f` +* [mod-rxvertx - Extension for VertX](https://github.com/vert-x/mod-rxvertx) that provides support for Reactive Extensions (RX) using the RxJava library +* [rxjava-jdbc](https://github.com/davidmoten/rxjava-jdbc) - use RxJava with jdbc connections to stream ResultSets and do functional composition of statements +* [rtree](https://github.com/davidmoten/rtree) - immutable in-memory R-tree and R*-tree with RxJava api including backpressure \ No newline at end of file diff --git a/docs/Reactive-Streams.md b/docs/Reactive-Streams.md new file mode 100644 index 0000000000..c3a65b883a --- /dev/null +++ b/docs/Reactive-Streams.md @@ -0,0 +1,121 @@ +# Reactive Streams + RxJava + +[Reactive Streams](https://github.com/reactive-streams/reactive-streams-jvm/) has been a [collaborative effort](https://medium.com/@viktorklang/reactive-streams-1-0-0-interview-faaca2c00bec) to standardize the protocol for asynchronous streams on the JVM. The RxJava team was [part of the effort](https://github.com/reactive-streams/reactive-streams-jvm/graphs/contributors) from the beginning and supports the use of Reactive Streams APIs and eventually the [Java 9 Flow APIs](http://cs.oswego.edu/pipermail/concurrency-interest/2015-January/013641.html) which are [resulting from the success of the Reactive Stream effort](https://github.com/reactive-streams/reactive-streams-jvm/issues/195). + +## How does this relate to RxJava itself? + +#### RxJava 1.x + +Currently RxJava 1.x does not directly implement the Reactive Streams APIs. This is due to RxJava 1.x already existing and not being able to break public APIs. It does however comply semantically with the non-blocking "reactive pull" approach to backpressure and flow control and thus can use a bridge between types. The [RxJavaReactiveStreams module](https://github.com/ReactiveX/RxJavaReactiveStreams) bridges between the RxJava 1.x types and Reactive Streams types for interop between Reactive Streams implementations and passes the Reactive Streams [TCK compliance tests](https://github.com/ReactiveX/RxJavaReactiveStreams/blob/0.x/rxjava-reactive-streams/build.gradle#L8). + +Its API looks like this: + +```java +package rx; + +import org.reactivestreams.Publisher; + +public abstract class RxReactiveStreams { + + public static <T> Publisher<T> toPublisher(Observable<T> observable) { … } + + public static <T> Observable<T> toObservable(Publisher<T> publisher) { … } + +} +``` + +#### RxJava 2.x + +[RxJava 2.x](https://github.com/ReactiveX/RxJava/issues/2450) will target Reactive Streams APIs directly for Java 8+. The plan is to also support Java 9 `j.u.c.Flow` types by leveraging new Java multi-versioned jars to support this when using RxJava 2.x in Java 9 while still working on Java 8. + +RxJava 2 will truly be "Reactive Extensions" now that there is an interface to extend. RxJava 1 didn't have a base interface or contract to extend so had to define it from scratch. RxJava 2 intends on being a high performing, battle-tested, lightweight (single dependency on Reactive Streams), non-opinionated implementation of Reactive Streams and `j.u.c.Flow` that provides a library of higher-order functions with parameterized concurrency. + +## Public APIs of Libraries + +A strong area of value for Reactive Streams is public APIs exposed in libraries. Following is some guidance and recommendation on how to use both Reactive Streams and RxJava in creating reactive libraries while decoupling the concrete implementations. + +### Pros of Exposing Reactive Stream APIs instead of RxJava + +* Lightweight: Very lightweight dependency on interfaces without any concrete implementations. This keeps dependency graphs and bytesize small. +* Future Proof: Since the Reactive Stream API is so simple, was collaboratively defined and is [becoming part](https://github.com/reactive-streams/reactive-streams-jvm/issues/195) of [JDK 9](http://cs.oswego.edu/pipermail/concurrency-interest/2015-January/013641.html) it is a future proof API for exposing async access to data. The [`j.u.c.Flow` APIs](http://gee.cs.oswego.edu/dl/jsr166/dist/docs/java/util/concurrent/Flow.html) of JDK 9 match the APIs of Reactive Streams so any types that implement the Reactive Streams `Publisher` will also be able to implement the `Flow.Publisher` type. +* Interop: An API exposed with Reactive Streams types can easily be consumed by any implementation such as RxJava, Akka Streams and Reactor. + +### Cons of Exposing Reactive Stream APIs instead of RxJava + +* A Reactive Stream `Publisher` is not very useful by itself. Without higher-order functions like `flatMap` it is just a better callback. This means that consumption of a `Publisher` will almost always need to be converted or wrapped into a Reactive Stream implementation. This can be verbose and awkward to always be wrapping `Publisher` APIs into a concrete implementation. If the JVM supported extension methods this would be elegant, but since it doesn't it is explicit and verbose. + + Specifically the Reactive Streams and Flow `Publisher` interfaces do not provide any implementations of operators like `flatMap`, `merge`, `filter`, `take`, `zip` and the many others used to compose and transform async streams. A `Publisher` can only be subscribed to. A concrete implementation such as RxJava is needed to provide composition. +* The Reactive Streams specification and binary artifacts do not provide a concrete implementation of `Publisher`. Generally a library will need or want capabilities provides by RxJava, Akka Streams, etc for its internal use or just to produce a valid `Publisher` that supports backpressure semantics (which are non-trivial to implement correctly). + +### Recommended Approach + +Now that Reactive Streams has achieved 1.0 we recommend using it for core APIs that are intended for interop. This will allow embracing asynchronous stream semantics without a hard dependency on any single implementation. This means a consumer of the API can then choose RxJava 1.x, RxJava 2.x, Akka Streams, Reactor or other stream composition libraries as suits them best. It also provides better future proofing, for example as RxJava moves from 1.x to 2.x. + +However, to limit the cons listed above, we also recommend making it easy for consumption without developers needing to explicitly wrap the APIs with their composition library of choice. For this reason we recommend providing wrapper modules for popular Reactive Stream implementations on top of the core API, otherwise your customers will each need to do this themselves. + +Note that if Java offered extension methods this approach wouldn't be needed, but until Java offers that (not anytime soon if ever) the following is an approach to achieve the pros and address the cons. + +For example, a database driver may have modules such as this: + + +// core library exposing Reactive Stream Publisher APIs +* async-database-driver + +// integration jars wrapped with concrete implementations +* async-database-driver-rxjava1 +* async-database-driver-rxjava2 +* async-database-driver-akka-stream + +The "core" may expose an API like this: + +```java +package com.database.driver; + +public class Database { + public org.reactivestreams.Publisher getValue(String key); +} +``` + +The RxJava 1.x wrapper could then be a separate module that provides RxJava specific APIs like this: + +```java +package com.database.driver.rxjava1; + +public class Database { + public rx.Observable getValue(String key); +} +``` + +The core `Publisher` API can be wrapped as simply as this: + +```java +public rx.Observable getValue(String key) { + return RxReactiveStreams.toObservable(coreDatabase.getValue(key)); +} +``` + +The RxJava 2.x wrapper would differ like this (once 2.x is available): + +```java +package com.database.driver.rxjava2; + +public class Database { + public io.reactivex.Observable getValue(String key); +} +``` + +The Akka Streams wrapper would in turn look like this: + +```java +package com.database.driver.akkastream; + +public class Database { + public akka.stream.javadsl.Source getValue(String key); +} +``` + +A developer could then choose to depend directly on the `async-database-driver` APIs but most will use one of the wrappers that supports the composition library they have chosen. + +---- + +If something could be clarified further, please help improve this page via discussion at https://github.com/ReactiveX/RxJava/issues/2917 \ No newline at end of file diff --git a/docs/Scheduler.md b/docs/Scheduler.md new file mode 100644 index 0000000000..95657cc488 --- /dev/null +++ b/docs/Scheduler.md @@ -0,0 +1,3 @@ +If you want to introduce multithreading into your cascade of Observable operators, you can do so by instructing those operators (or particular Observables) to operate on particular Schedulers. + +For more information about Schedulers, see [the ReactiveX `Scheduler` documentation page](http://reactivex.io/documentation/scheduler.html). \ No newline at end of file diff --git a/docs/String-Observables.md b/docs/String-Observables.md new file mode 100644 index 0000000000..3dc3038b94 --- /dev/null +++ b/docs/String-Observables.md @@ -0,0 +1,9 @@ +The `StringObservable` class contains methods that represent operators particular to Observables that deal in string-based sequences and streams. These include: + +* [**`byLine( )`**](http://reactivex.io/documentation/operators/map.html) — converts an Observable of Strings into an Observable of Lines by treating the source sequence as a stream and splitting it on line-endings +* [**`decode( )`**](http://reactivex.io/documentation/operators/from.html) — convert a stream of multibyte characters into an Observable that emits byte arrays that respect character boundaries +* [**`encode( )`**](http://reactivex.io/documentation/operators/map.html) — transform an Observable that emits strings into an Observable that emits byte arrays that respect character boundaries of multibyte characters in the original strings +* [**`from( )`**](http://reactivex.io/documentation/operators/from.html) — convert a stream of characters or a Reader into an Observable that emits byte arrays or Strings +* [**`join( )`**](http://reactivex.io/documentation/operators/sum.html) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all, separating them by a specified string +* [**`split( )`**](http://reactivex.io/documentation/operators/flatmap.html) — converts an Observable of Strings into an Observable of Strings that treats the source sequence as a stream and splits it on a specified regex boundary +* [**`stringConcat( )`**](http://reactivex.io/documentation/operators/sum.html) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all \ No newline at end of file diff --git a/docs/Subject.md b/docs/Subject.md new file mode 100644 index 0000000000..ffb7feeb51 --- /dev/null +++ b/docs/Subject.md @@ -0,0 +1,12 @@ +A <a href="http://reactivex.io/RxJava/javadoc/rx/subjects/Subject.html">`Subject`</a> is a sort of bridge or proxy that acts both as an `Subscriber` and as an `Observable`. Because it is a Subscriber, it can subscribe to one or more Observables, and because it is an Observable, it can pass through the items it observes by reemitting them, and it can also emit new items. + +For more information about the varieties of Subject and how to use them, see [the ReactiveX `Subject` documentation](http://reactivex.io/documentation/subject.html). + +#### Serializing +When you use a Subject as a Subscriber, take care not to call its `onNext( )` method (or its other `on` methods) from multiple threads, as this could lead to non-serialized calls, which violates the Observable contract and creates an ambiguity in the resulting Subject. + +To protect a Subject from this danger, you can convert it into a [`SerializedSubject`](http://reactivex.io/RxJava/javadoc/rx/subjects/SerializedSubject.html) with code like the following: + +```java +mySafeSubject = new SerializedSubject( myUnsafeSubject ); +``` diff --git a/docs/The-RxJava-Android-Module.md b/docs/The-RxJava-Android-Module.md new file mode 100644 index 0000000000..ad9817c80a --- /dev/null +++ b/docs/The-RxJava-Android-Module.md @@ -0,0 +1,3 @@ +## RxAndroid + +See the [RxAndroid](https://github.com/ReactiveX/RxAndroid) project page and the [the RxAndroid wiki](https://github.com/ReactiveX/RxAndroid/wiki) for details. diff --git a/docs/Transforming-Observables.md b/docs/Transforming-Observables.md new file mode 100644 index 0000000000..3a3d94cd2e --- /dev/null +++ b/docs/Transforming-Observables.md @@ -0,0 +1,777 @@ +This page shows operators with which you can transform items that are emitted by reactive sources, such as `Observable`s. + +# Outline + +- [`buffer`](#buffer) +- [`cast`](#cast) +- [`concatMap`](#concatmap) +- [`concatMapCompletable`](#concatmapcompletable) +- [`concatMapCompletableDelayError`](#concatmapcompletabledelayerror) +- [`concatMapDelayError`](#concatmapdelayerror) +- [`concatMapEager`](#concatmapeager) +- [`concatMapEagerDelayError`](#concatmapeagerdelayerror) +- [`concatMapIterable`](#concatmapiterable) +- [`concatMapMaybe`](#concatmapmaybe) +- [`concatMapMaybeDelayError`](#concatmapmaybedelayerror) +- [`concatMapSingle`](#concatmapsingle) +- [`concatMapSingleDelayError`](#concatmapsingledelayerror) +- [`flatMap`](#flatmap) +- [`flatMapCompletable`](#flatmapcompletable) +- [`flatMapIterable`](#flatmapiterable) +- [`flatMapMaybe`](#flatmapmaybe) +- [`flatMapObservable`](#flatmapobservable) +- [`flatMapPublisher`](#flatmappublisher) +- [`flatMapSingle`](#flatmapsingle) +- [`flatMapSingleElement`](#flatmapsingleelement) +- [`flattenAsFlowable`](#flattenasflowable) +- [`flattenAsObservable`](#flattenasobservable) +- [`groupBy`](#groupby) +- [`map`](#map) +- [`scan`](#scan) +- [`switchMap`](#switchmap) +- [`window`](#window) + +## buffer + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/buffer.html](http://reactivex.io/documentation/operators/buffer.html) + +Collects the items emitted by a reactive source into buffers, and emits these buffers. + +### buffer example + +```java +Observable.range(0, 10) + .buffer(4) + .subscribe((List<Integer> buffer) -> System.out.println(buffer)); + +// prints: +// [0, 1, 2, 3] +// [4, 5, 6, 7] +// [8, 9] +``` + +## cast + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/map.html](http://reactivex.io/documentation/operators/map.html) + +Converts each item emitted by a reactive source to the specified type, and emits these items. + +### cast example + +```java +Observable<Number> numbers = Observable.just(1, 4.0, 3f, 7, 12, 4.6, 5); + +numbers.filter((Number x) -> Integer.class.isInstance(x)) + .cast(Integer.class) + .subscribe((Integer x) -> System.out.println(x)); + +// prints: +// 1 +// 7 +// 12 +// 5 +``` + +## concatMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. + +### concatMap example + +```java +Observable.range(0, 5) + .concatMap(i -> { + long delay = Math.round(Math.random() * 2); + + return Observable.timer(delay, TimeUnit.SECONDS).map(n -> i); + }) + .blockingSubscribe(System.out::print); + +// prints 01234 +``` + +## concatMapCompletable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, subscribes to them one at a time and returns a `Completable` that completes when all sources completed. + +### concatMapCompletable example + +```java +Observable<Integer> source = Observable.just(2, 1, 3); +Completable completable = source.concatMapCompletable(x -> { + return Completable.timer(x, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); + }); + +completable.doOnComplete(() -> System.out.println("Info: Processing of all items completed")) + .blockingAwait(); + +// prints: +// Info: Processing of item "2" completed +// Info: Processing of item "1" completed +// Info: Processing of item "3" completed +// Info: Processing of all items completed +``` + +## concatMapCompletableDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, subscribes to them one at a time and returns a `Completable` that completes when all sources completed. Any errors from the sources will be delayed until all of them terminate. + +### concatMapCompletableDelayError example + +```java +Observable<Integer> source = Observable.just(2, 1, 3); +Completable completable = source.concatMapCompletableDelayError(x -> { + if (x.equals(2)) { + return Completable.error(new IOException("Processing of item \"" + x + "\" failed!")); + } else { + return Completable.timer(1, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); + } +}); + +completable.doOnError(error -> System.out.println("Error: " + error.getMessage())) + .onErrorComplete() + .blockingAwait(); + +// prints: +// Info: Processing of item "1" completed +// Info: Processing of item "3" completed +// Error: Processing of item "2" failed! +``` + +## concatMapDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. Any errors from the sources will be delayed until all of them terminate. + +### concatMapDelayError example + +```java +Observable.intervalRange(1, 3, 0, 1, TimeUnit.SECONDS) + .concatMapDelayError(x -> { + if (x.equals(1L)) return Observable.error(new IOException("Something went wrong!")); + else return Observable.just(x, x * x); + }) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2 +// onNext: 4 +// onNext: 3 +// onNext: 9 +// onError: Something went wrong! +``` + +## concatMapEager + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. Unlike [`concatMap`](#concatmap), this operator eagerly subscribes to all sources. + +### concatMapEager example + +```java +Observable.range(0, 5) + .concatMapEager(i -> { + long delay = Math.round(Math.random() * 3); + + return Observable.timer(delay, TimeUnit.SECONDS) + .map(n -> i) + .doOnNext(x -> System.out.println("Info: Finished processing item " + x)); + }) + .blockingSubscribe(i -> System.out.println("onNext: " + i)); + +// prints (lines beginning with "Info..." can be displayed in a different order): +// Info: Finished processing item 2 +// Info: Finished processing item 0 +// onNext: 0 +// Info: Finished processing item 1 +// onNext: 1 +// onNext: 2 +// Info: Finished processing item 3 +// Info: Finished processing item 4 +// onNext: 3 +// onNext: 4 +``` + +## concatMapEagerDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from concatenating the results of these function applications. A `boolean` value must be specified, which if `true` indicates that all errors from all sources will be delayed until the end, otherwise if `false`, an error from the main source will be signalled when the current source terminates. Unlike [concatMapDelayError](#concatmapdelayerror), this operator eagerly subscribes to all sources. + +### concatMapEagerDelayError example + +```java +Observable<Integer> source = Observable.create(emitter -> { + emitter.onNext(1); + emitter.onNext(2); + emitter.onError(new Error("Fatal error!")); +}); + +source.doOnError(error -> System.out.println("Info: Error from main source " + error.getMessage())) + .concatMapEagerDelayError(x -> { + return Observable.timer(1, TimeUnit.SECONDS).map(n -> x) + .doOnSubscribe(it -> System.out.println("Info: Processing of item \"" + x + "\" started")); + }, true) + .blockingSubscribe( + x -> System.out.println("onNext: " + x), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// Info: Processing of item "1" started +// Info: Processing of item "2" started +// Info: Error from main source Fatal error! +// onNext: 1 +// onNext: 2 +// onError: Fatal error! +``` + +## concatMapIterable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `java.lang.Iterable`, and emits the items that result from concatenating the results of these function applications. + +### concatMapIterable example + +```java +Observable.just("A", "B", "C") + .concatMapIterable(item -> List.of(item, item, item)) + .subscribe(System.out::print); + +// prints AAABBBCCC +``` + +## concatMapMaybe + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from concatenating these `MaybeSource`s. + +### concatMapMaybe example + +```java +Observable.just("5", "3,14", "2.71", "FF") + .concatMapMaybe(v -> { + return Maybe.fromCallable(() -> Double.parseDouble(v)) + .doOnError(e -> System.out.println("Info: The value \"" + v + "\" could not be parsed.")) + + // Ignore values that can not be parsed. + .onErrorComplete(); + }) + .subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 5.0 +// Info: The value "3,14" could not be parsed. +// onNext: 2.71 +// Info: The value "FF" could not be parsed. +``` + +## concatMapMaybeDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from concatenating these `MaybeSource`s. Any errors from the sources will be delayed until all of them terminate. + +### concatMapMaybeDelayError example + +```java +DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.uuuu"); +Observable.just("04.03.2018", "12-08-2018", "06.10.2018", "01.12.2018") + .concatMapMaybeDelayError(date -> { + return Maybe.fromCallable(() -> LocalDate.parse(date, dateFormatter)); + }) + .subscribe( + localDate -> System.out.println("onNext: " + localDate), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2018-03-04 +// onNext: 2018-10-06 +// onNext: 2018-12-01 +// onError: Text '12-08-2018' could not be parsed at index 2 +``` + +## concatMapSingle + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from concatenating these ``SingleSource`s. + +### concatMapSingle example + +```java +Observable.just("5", "3,14", "2.71", "FF") + .concatMapSingle(v -> { + return Single.fromCallable(() -> Double.parseDouble(v)) + .doOnError(e -> System.out.println("Info: The value \"" + v + "\" could not be parsed.")) + + // Return a default value if the given value can not be parsed. + .onErrorReturnItem(42.0); + }) + .subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 5.0 +// Info: The value "3,14" could not be parsed. +// onNext: 42.0 +// onNext: 2.71 +// Info: The value "FF" could not be parsed. +// onNext: 42.0 +``` + +## concatMapSingleDelayError + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from concatenating the results of these function applications. Any errors from the sources will be delayed until all of them terminate. + +### concatMapSingleDelayError example + +```java +DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.uuuu"); +Observable.just("24.03.2018", "12-08-2018", "06.10.2018", "01.12.2018") + .concatMapSingleDelayError(date -> { + return Single.fromCallable(() -> LocalDate.parse(date, dateFormatter)); + }) + .subscribe( + localDate -> System.out.println("onNext: " + localDate), + error -> System.out.println("onError: " + error.getMessage())); + +// prints: +// onNext: 2018-03-24 +// onNext: 2018-10-06 +// onNext: 2018-12-01 +// onError: Text '12-08-2018' could not be parsed at index 2 +``` + +## flatMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items that result from merging the results of these function applications. + +### flatMap example + +```java +Observable.just("A", "B", "C") + .flatMap(a -> { + return Observable.intervalRange(1, 3, 0, 1, TimeUnit.SECONDS) + .map(b -> '(' + a + ", " + b + ')'); + }) + .blockingSubscribe(System.out::println); + +// prints (not necessarily in this order): +// (A, 1) +// (C, 1) +// (B, 1) +// (A, 2) +// (C, 2) +// (B, 2) +// (A, 3) +// (C, 3) +// (B, 3) +``` + +## flatMapCompletable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.CompletableSource`, and returns a `Completable` that completes when all sources completed. + +### flatMapCompletable example + +```java +Observable<Integer> source = Observable.just(2, 1, 3); +Completable completable = source.flatMapCompletable(x -> { + return Completable.timer(x, TimeUnit.SECONDS) + .doOnComplete(() -> System.out.println("Info: Processing of item \"" + x + "\" completed")); +}); + +completable.doOnComplete(() -> System.out.println("Info: Processing of all items completed")) + .blockingAwait(); + +// prints: +// Info: Processing of item "1" completed +// Info: Processing of item "2" completed +// Info: Processing of item "3" completed +// Info: Processing of all items completed +``` + +## flatMapIterable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `java.lang.Iterable`, and emits the elements from these `Iterable`s. + +### flatMapIterable example + +```java +Observable.just(1, 2, 3, 4) + .flatMapIterable(x -> { + switch (x % 4) { + case 1: + return List.of("A"); + case 2: + return List.of("B", "B"); + case 3: + return List.of("C", "C", "C"); + default: + return List.of(); + } + }) + .subscribe(System.out::println); + +// prints: +// A +// B +// B +// C +// C +// C +``` + +## flatMapMaybe + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.MaybeSource`, and emits the items that result from merging these `MaybeSource`s. + +### flatMapMaybe example + +```java +Observable.just(9.0, 16.0, -4.0) + .flatMapMaybe(x -> { + if (x.compareTo(0.0) < 0) return Maybe.empty(); + else return Maybe.just(Math.sqrt(x)); + }) + .subscribe( + System.out::println, + Throwable::printStackTrace, + () -> System.out.println("onComplete")); + +// prints: +// 3.0 +// 4.0 +// onComplete +``` + +## flatMapObservable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns an `io.reactivex.ObservableSource`, and returns an `Observable` that emits the items emitted by this `ObservableSource`. + +### flatMapObservable example + +```java +Single<String> source = Single.just("Kirk, Spock, Chekov, Sulu"); +Observable<String> names = source.flatMapObservable(text -> { + return Observable.fromArray(text.split(",")) + .map(String::strip); +}); + +names.subscribe(name -> System.out.println("onNext: " + name)); + +// prints: +// onNext: Kirk +// onNext: Spock +// onNext: Chekov +// onNext: Sulu +``` + +## flatMapPublisher + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns an `org.reactivestreams.Publisher`, and returns a `Flowable` that emits the items emitted by this `Publisher`. + +### flatMapPublisher example + +```java +Single<String> source = Single.just("Kirk, Spock, Chekov, Sulu"); +Flowable<String> names = source.flatMapPublisher(text -> { + return Flowable.fromArray(text.split(",")) + .map(String::strip); +}); + +names.subscribe(name -> System.out.println("onNext: " + name)); + +// prints: +// onNext: Kirk +// onNext: Spock +// onNext: Chekov +// onNext: Sulu +``` + +## flatMapSingle + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a `io.reactivex.SingleSource`, and emits the items that result from merging these `SingleSource`s. + +### flatMapSingle example + +```java +Observable.just(4, 2, 1, 3) + .flatMapSingle(x -> Single.timer(x, TimeUnit.SECONDS).map(i -> x)) + .blockingSubscribe(System.out::print); + +// prints 1234 +``` + +*Note:* `Maybe::flatMapSingle` returns a `Single` that signals an error notification if the `Maybe` source is empty: + +```java +Maybe<Object> emptySource = Maybe.empty(); +Single<Object> result = emptySource.flatMapSingle(x -> Single.just(x)); +result.subscribe( + x -> System.out.println("onSuccess will not be printed!"), + error -> System.out.println("onError: Source was empty!")); + +// prints: +// onError: Source was empty! +``` + +Use [`Maybe::flatMapSingleElement`](#flatmapsingleelement) -- which returns a `Maybe` -- if you don't want this behaviour. + +## flatMapSingleElement + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe`, where that function returns a `io.reactivex.SingleSource`, and returns a `Maybe` that either emits the item emitted by this `SingleSource` or completes if the source `Maybe` just completes. + +### flatMapSingleElement example + +```java +Maybe<Integer> source = Maybe.just(-42); +Maybe<Integer> result = source.flatMapSingleElement(x -> { + return Single.just(Math.abs(x)); +}); + +result.subscribe(System.out::println); + +// prints 42 +``` + +## flattenAsFlowable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns a `java.lang.Iterable`, and returns a `Flowable` that emits the elements from this `Iterable`. + +### flattenAsFlowable example + +```java +Single<Double> source = Single.just(2.0); +Flowable<Double> flowable = source.flattenAsFlowable(x -> { + return List.of(x, Math.pow(x, 2), Math.pow(x, 3)); +}); + +flowable.subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 2.0 +// onNext: 4.0 +// onNext: 8.0 +``` + +## flattenAsObservable + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to the item emitted by a `Maybe` or `Single`, where that function returns a `java.lang.Iterable`, and returns an `Observable` that emits the elements from this `Iterable`. + +### flattenAsObservable example + +```java +Single<Double> source = Single.just(2.0); +Observable<Double> observable = source.flattenAsObservable(x -> { + return List.of(x, Math.pow(x, 2), Math.pow(x, 3)); +}); + +observable.subscribe(x -> System.out.println("onNext: " + x)); + +// prints: +// onNext: 2.0 +// onNext: 4.0 +// onNext: 8.0 +``` + +## groupBy + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/groupby.html](http://reactivex.io/documentation/operators/groupby.html) + +Groups the items emitted by a reactive source according to a specified criterion, and emits these grouped items as a `GroupedObservable` or `GroupedFlowable`. + +### groupBy example + +```java +Observable<String> animals = Observable.just( + "Tiger", "Elephant", "Cat", "Chameleon", "Frog", "Fish", "Turtle", "Flamingo"); + +animals.groupBy(animal -> animal.charAt(0), String::toUpperCase) + .concatMapSingle(Observable::toList) + .subscribe(System.out::println); + +// prints: +// [TIGER, TURTLE] +// [ELEPHANT] +// [CAT, CHAMELEON] +// [FROG, FISH, FLAMINGO] +``` + +## map + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/map.html](http://reactivex.io/documentation/operators/map.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source and emits the results of these function applications. + +### map example + +```java +Observable.just(1, 2, 3) + .map(x -> x * x) + .subscribe(System.out::println); + +// prints: +// 1 +// 4 +// 9 +``` + +## scan + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/scan.html](http://reactivex.io/documentation/operators/scan.html) + +Applies the given `io.reactivex.functions.BiFunction` to a seed value and the first item emitted by a reactive source, then feeds the result of that function application along with the second item emitted by the reactive source into the same function, and so on until all items have been emitted by the reactive source, emitting each intermediate result. + +### scan example + +```java +Observable.just(5, 3, 8, 1, 7) + .scan(0, (partialSum, x) -> partialSum + x) + .subscribe(System.out::println); + +// prints: +// 0 +// 5 +// 8 +// 16 +// 17 +// 24 +``` + +## switchMap + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/flatmap.html](http://reactivex.io/documentation/operators/flatmap.html) + +Applies the given `io.reactivex.functions.Function` to each item emitted by a reactive source, where that function returns a reactive source, and emits the items emitted by the most recently projected of these reactive sources. + +### switchMap example + +```java +Observable.interval(0, 1, TimeUnit.SECONDS) + .switchMap(x -> { + return Observable.interval(0, 750, TimeUnit.MILLISECONDS) + .map(y -> x); + }) + .takeWhile(x -> x < 3) + .blockingSubscribe(System.out::print); + +// prints 001122 +``` + +## window + +**Available in:** ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Flowable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_on.png) `Observable`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Maybe`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Single`, ![image](https://raw.github.com/wiki/ReactiveX/RxJava/images/checkmark_off.png) `Completable` + +**ReactiveX documentation:** [http://reactivex.io/documentation/operators/window.html](http://reactivex.io/documentation/operators/window.html) + +Collects the items emitted by a reactive source into windows, and emits these windows as a `Flowable` or `Observable`. + +### window example + +```java +Observable.range(1, 10) + + // Create windows containing at most 2 items, and skip 3 items before starting a new window. + .window(2, 3) + .flatMapSingle(window -> { + return window.map(String::valueOf) + .reduce(new StringJoiner(", ", "[", "]"), StringJoiner::add); + }) + .subscribe(System.out::println); + +// prints: +// [1, 2] +// [4, 5] +// [7, 8] +// [10] +``` diff --git a/docs/What's-different-in-2.0.md b/docs/What's-different-in-2.0.md new file mode 100644 index 0000000000..30f9a4ccba --- /dev/null +++ b/docs/What's-different-in-2.0.md @@ -0,0 +1,970 @@ +RxJava 2.0 has been completely rewritten from scratch on top of the Reactive Streams specification. The specification itself has evolved out of RxJava 1.x and provides a common baseline for reactive systems and libraries. + +Because Reactive Streams has a different architecture, it mandates changes to some well known RxJava types. This wiki page attempts to summarize what has changed and describes how to rewrite 1.x code into 2.x code. + +For technical details on how to write operators for 2.x, please visit the [Writing Operators](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0) wiki page. + +# Contents + + - [Maven address and base package](#maven-address-and-base-package) + - [Javadoc](#javadoc) + - [Nulls](#nulls) + - [Observable and Flowable](#observable-and-flowable) + - [Single](#single) + - [Completable](#completable) + - [Maybe](#maybe) + - [Base reactive interfaces](#base-reactive-interfaces) + - [Subjects and Processors](#subjects-and-processors) + - [Other classes](#other-classes) + - [Functional interfaces](#functional-interfaces) + - [Subscriber](#subscriber) + - [Subscription](#subscription) + - [Backpressure](#backpressure) + - [Reactive Streams compliance](#reactive-streams-compliance) + - [Runtime hooks](#runtime-hooks) + - [Error handling](#error-handling) + - [Scheduler](#schedulers) + - [Entering the reactive world](#entering-the-reactive-world) + - [Leaving the reactive world](#leaving-the-reactive-world) + - [Testing](#testing) + - [Operator differences](#operator-differences) + - [Miscellaneous changes](#miscellaneous-changes) + + +# Maven address and base package + +To allow having RxJava 1.x and RxJava 2.x side-by-side, RxJava 2.x is under the maven coordinates `io.reactivex.rxjava2:rxjava:2.x.y` and classes are accessible below `io.reactivex`. + +Users switching from 1.x to 2.x have to re-organize their imports, but carefully. + +# Javadoc + +The official Javadoc pages for 2.x is hosted at http://reactivex.io/RxJava/2.x/javadoc/ + +# Nulls + +RxJava 2.x no longer accepts `null` values and the following will yield `NullPointerException` immediately or as a signal to downstream: + +```java +Observable.just(null); + +Single.just(null); + +Observable.fromCallable(() -> null) + .subscribe(System.out::println, Throwable::printStackTrace); + +Observable.just(1).map(v -> null) + .subscribe(System.out::println, Throwable::printStackTrace); +``` + +This means that `Observable<Void>` can no longer emit any values but only terminate normally or with an exception. API designers may instead choose to define `Observable<Object>` with no guarantee on what `Object` will be (which should be irrelevant anyway). For example, if one needs a signaller-like source, a shared enum can be defined and its solo instance `onNext`'d: + +```java +enum Irrelevant { INSTANCE; } + +Observable<Object> source = Observable.create((ObservableEmitter<Object> emitter) -> { + System.out.println("Side-effect 1"); + emitter.onNext(Irrelevant.INSTANCE); + + System.out.println("Side-effect 2"); + emitter.onNext(Irrelevant.INSTANCE); + + System.out.println("Side-effect 3"); + emitter.onNext(Irrelevant.INSTANCE); +}); + +source.subscribe(e -> { /* Ignored. */ }, Throwable::printStackTrace); +``` + +# Observable and Flowable + +A small regret about introducing backpressure in RxJava 0.x is that instead of having a separate base reactive class, the `Observable` itself was retrofitted. The main issue with backpressure is that many hot sources, such as UI events, can't be reasonably backpressured and cause unexpected `MissingBackpressureException` (i.e., beginners don't expect them). + +We try to remedy this situation in 2.x by having `io.reactivex.Observable` non-backpressured and the new `io.reactivex.Flowable` be the backpressure-enabled base reactive class. + +The good news is that operator names remain (mostly) the same. The bad news is that one should be careful when performing 'organize imports' as it may select the non-backpressured `io.reactivex.Observable` unintended. + +## Which type to use? + +When architecting dataflows (as an end-consumer of RxJava) or deciding upon what type your 2.x compatible library should take and return, you can consider a few factors that should help you avoid problems down the line such as `MissingBackpressureException` or `OutOfMemoryError`. + +### When to use Observable + + - You have a flow of no more than 1000 elements at its longest: i.e., you have so few elements over time that there is practically no chance for OOME in your application. + - You deal with GUI events such as mouse moves or touch events: these can rarely be backpressured reasonably and aren't that frequent. You may be able to handle an element frequency of 1000 Hz or less with Observable but consider using sampling/debouncing anyway. + - Your flow is essentially synchronous but your platform doesn't support Java Streams or you miss features from it. Using `Observable` has lower overhead in general than `Flowable`. *(You could also consider IxJava which is optimized for Iterable flows supporting Java 6+)*. + +### When to use Flowable + + - Dealing with 10k+ of elements that are generated in some fashion somewhere and thus the chain can tell the source to limit the amount it generates. + - Reading (parsing) files from disk is inherently blocking and pull-based which works well with backpressure as you control, for example, how many lines you read from this for a specified request amount). + - Reading from a database through JDBC is also blocking and pull-based and is controlled by you by calling `ResultSet.next()` for likely each downstream request. + - Network (Streaming) IO where either the network helps or the protocol used supports requesting some logical amount. + - Many blocking and/or pull-based data sources which may eventually get a non-blocking reactive API/driver in the future. + + +# Single + +The 2.x `Single` reactive base type, which can emit a single `onSuccess` or `onError` has been redesigned from scratch. Its architecture now derives from the Reactive Streams design. Its consumer type (`rx.Single.SingleSubscriber<T>`) has been changed from being a class that accepts `rx.Subscription` resources to be an interface `io.reactivex.SingleObserver<T>` that has only 3 methods: + +```java +interface SingleObserver<T> { + void onSubscribe(Disposable d); + void onSuccess(T value); + void onError(Throwable error); +} +``` + +and follows the protocol `onSubscribe (onSuccess | onError)?`. + +# Completable + +The `Completable` type remains largely the same. It was already designed along the Reactive Streams style for 1.x so no user-level changes there. + +Similar to the naming changes, `rx.Completable.CompletableSubscriber` has become `io.reactivex.CompletableObserver` with `onSubscribe(Disposable)`: + +```java +interface CompletableObserver<T> { + void onSubscribe(Disposable d); + void onComplete(); + void onError(Throwable error); +} +``` + +and still follows the protocol `onSubscribe (onComplete | onError)?`. + +# Maybe + +RxJava 2.0.0-RC2 introduced a new base reactive type called `Maybe`. Conceptually, it is a union of `Single` and `Completable` providing the means to capture an emission pattern where there could be 0 or 1 item or an error signalled by some reactive source. + +The `Maybe` class is accompanied by `MaybeSource` as its base interface type, `MaybeObserver<T>` as its signal-receiving interface and follows the protocol `onSubscribe (onSuccess | onError | onComplete)?`. Because there could be at most 1 element emitted, the `Maybe` type has no notion of backpressure (because there is no buffer bloat possible as with unknown length `Flowable`s or `Observable`s. + +This means that an invocation of `onSubscribe(Disposable)` is potentially followed by one of the other `onXXX` methods. Unlike `Flowable`, if there is only a single value to be signalled, only `onSuccess` is called and `onComplete` is not. + +Working with this new base reactive type is practically the same as the others as it offers a modest subset of the `Flowable` operators that make sense with a 0 or 1 item sequence. + +```java +Maybe.just(1) +.map(v -> v + 1) +.filter(v -> v == 1) +.defaultIfEmpty(2) +.test() +.assertResult(2); +``` + +# Base reactive interfaces + +Following the style of extending the Reactive Streams `Publisher<T>` in `Flowable`, the other base reactive classes now extend similar base interfaces (in package `io.reactivex`): + +```java +interface ObservableSource<T> { + void subscribe(Observer<? super T> observer); +} + +interface SingleSource<T> { + void subscribe(SingleObserver<? super T> observer); +} + +interface CompletableSource { + void subscribe(CompletableObserver observer); +} + +interface MaybeSource<T> { + void subscribe(MaybeObserver<? super T> observer); +} +``` + +Therefore, many operators that required some reactive base type from the user now accept `Publisher` and `XSource`: + +```java +Flowable<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper); + +Observable<R> flatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper); +``` + +By having `Publisher` as input this way, you can compose with other Reactive Streams compliant libraries without the need to wrap them or convert them into `Flowable` first. + +If an operator has to offer a reactive base type, however, the user will receive the full reactive class (as giving out an `XSource` is practically useless as it doesn't have operators on it): + +```java +Flowable<Flowable<Integer>> windows = source.window(5); + +source.compose((Flowable<T> flowable) -> + flowable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread())); +``` + +# Subjects and Processors + +In the Reactive Streams specification, the `Subject`-like behavior, namely being a consumer and supplier of events at the same time, is done by the `org.reactivestreams.Processor` interface. As with the `Observable`/`Flowable` split, the backpressure-aware, Reactive Streams compliant implementations are based on the `FlowableProcessor<T>` class (which extends `Flowable` to give a rich set of instance operators). An important change regarding `Subject`s (and by extension, `FlowableProcessor`) that they no longer support `T -> R` like conversion (that is, input is of type `T` and the output is of type `R`). (We never had a use for it in 1.x and the original `Subject<T, R>` came from .NET where there is a `Subject<T>` overload because .NET allows the same class name with a different number of type arguments.) + +The `io.reactivex.subjects.AsyncSubject`, `io.reactivex.subjects.BehaviorSubject`, `io.reactivex.subjects.PublishSubject`, `io.reactivex.subjects.ReplaySubject` and `io.reactivex.subjects.UnicastSubject` in 2.x don't support backpressure (as part of the 2.x `Observable` family). + +The `io.reactivex.processors.AsyncProcessor`, `io.reactivex.processors.BehaviorProcessor`, `io.reactivex.processors.PublishProcessor`, `io.reactivex.processors.ReplayProcessor` and `io.reactivex.processors.UnicastProcessor` are backpressure-aware. The `BehaviorProcessor` and `PublishProcessor` don't coordinate requests (use `Flowable.publish()` for that) of their downstream subscribers and will signal them `MissingBackpressureException` if the downstream can't keep up. The other `XProcessor` types honor backpressure of their downstream subscribers but otherwise, when subscribed to a source (optional), they consume it in an unbounded manner (requesting `Long.MAX_VALUE`). + +## TestSubject + +The 1.x `TestSubject` has been dropped. Its functionality can be achieved via `TestScheduler`, `PublishProcessor`/`PublishSubject` and `observeOn(testScheduler)`/scheduler parameter. + +```java +TestScheduler scheduler = new TestScheduler(); +PublishSubject<Integer> ps = PublishSubject.create(); + +TestObserver<Integer> ts = ps.delay(1000, TimeUnit.MILLISECONDS, scheduler) +.test(); + +ts.assertEmpty(); + +ps.onNext(1); + +scheduler.advanceTimeBy(999, TimeUnit.MILLISECONDS); + +ts.assertEmpty(); + +scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + +ts.assertValue(1); +``` + +## SerializedSubject + +The `SerializedSubject` is no longer a public class. You have to use `Subject.toSerialized()` and `FlowableProcessor.toSerialized()` instead. + +# Other classes + +The `rx.observables.ConnectableObservable` is now `io.reactivex.observables.ConnectableObservable<T>` and `io.reactivex.flowables.ConnectableFlowable<T>`. + +## GroupedObservable + +The `rx.observables.GroupedObservable` is now `io.reactivex.observables.GroupedObservable<T>` and `io.reactivex.flowables.GroupedFlowable<T>`. + +In 1.x, you could create an instance with `GroupedObservable.from()` which was used internally by 1.x. In 2.x, all use cases now extend `GroupedObservable` directly thus the factory methods are no longer available; the whole class is now abstract. + +You can extend the class and add your own custom `subscribeActual` behavior to achieve something similar to the 1.x features: + +```java +class MyGroup<K, V> extends GroupedObservable<K, V> { + final K key; + + final Subject<V> subject; + + public MyGroup(K key) { + this.key = key; + this.subject = PublishSubject.create(); + } + + @Override + public T getKey() { + return key; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + subject.subscribe(observer); + } +} +``` + +(The same approach works with `GroupedFlowable` as well.) + +# Functional interfaces + +Because both 1.x and 2.x is aimed at Java 6+, we can't use the Java 8 functional interfaces such as `java.util.function.Function`. Instead, we defined our own functional interfaces in 1.x and 2.x follows this tradition. + +One notable difference is that all our functional interfaces now define `throws Exception`. This is a large convenience for consumers and mappers that otherwise throw and would need `try-catch` to transform or suppress a checked exception. + +```java +Flowable.just("file.txt") +.map(name -> Files.readLines(name)) +.subscribe(lines -> System.out.println(lines.size()), Throwable::printStackTrace); +``` + +If the file doesn't exist or can't be read properly, the end consumer will print out `IOException` directly. Note also the `Files.readLines(name)` invoked without try-catch. + +## Actions + +As the opportunity to reduce component count, 2.x doesn't define `Action3`-`Action9` and `ActionN` (these were unused within RxJava itself anyway). + +The remaining action interfaces were named according to the Java 8 functional types. The no argument `Action0` is replaced by the `io.reactivex.functions.Action` for the operators and `java.lang.Runnable` for the `Scheduler` methods. `Action1` has been renamed to `Consumer` and `Action2` is called `BiConsumer`. `ActionN` is replaced by the `Consumer<Object[]>` type declaration. + +## Functions + +We followed the naming convention of Java 8 by defining `io.reactivex.functions.Function` and `io.reactivex.functions.BiFunction`, plus renaming `Func3` - `Func9` into `Function3` - `Function9` respectively. The `FuncN` is replaced by the `Function<Object[], R>` type declaration. + +In addition, operators requiring a predicate no longer use `Func1<T, Boolean>` but have a separate, primitive-returning type of `Predicate<T>` (allows better inlining due to no autoboxing). + +# Subscriber + +The Reactive Streams specification has its own Subscriber as an interface. This interface is lightweight and combines request management with cancellation into a single interface `org.reactivestreams.Subscription` instead of having `rx.Producer` and `rx.Subscription` separately. This allows creating stream consumers with less internal state than the quite heavy `rx.Subscriber` of 1.x. + +```java +Flowable.range(1, 10).subscribe(new Subscriber<Integer>() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + System.out.println("Done"); + } +}); +``` + +Due to the name conflict, replacing the package from `rx` to `org.reactivestreams` is not enough. In addition, `org.reactivestreams.Subscriber` has no notion of adding resources to it, cancelling it or requesting from the outside. + +To bridge the gap we defined abstract classes `DefaultSubscriber`, `ResourceSubscriber` and `DisposableSubscriber` (plus their `XObserver` variants) for `Flowable` (and `Observable`) respectively that offers resource tracking support (of `Disposable`s) just like `rx.Subscriber` and can be cancelled/disposed externally via `dispose()`: + +```java +ResourceSubscriber<Integer> subscriber = new ResourceSubscriber<Integer>() { + @Override + public void onStart() { + request(Long.MAX_VALUE); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + + @Override + public void onComplete() { + System.out.println("Done"); + } +}; + +Flowable.range(1, 10).delay(1, TimeUnit.SECONDS).subscribe(subscriber); + +subscriber.dispose(); +``` + +Note also that due to Reactive Streams compatibility, the method `onCompleted` has been renamed to `onComplete` without the trailing `d`. + +Since 1.x `Observable.subscribe(Subscriber)` returned `Subscription`, users often added the `Subscription` to a `CompositeSubscription` for example: + +```java +CompositeSubscription composite = new CompositeSubscription(); + +composite.add(Observable.range(1, 5).subscribe(new TestSubscriber<Integer>())); +``` + +Due to the Reactive Streams specification, `Publisher.subscribe` returns void and the pattern by itself no longer works in 2.0. To remedy this, the method `E subscribeWith(E subscriber)` has been added to each base reactive class which returns its input subscriber/observer as is. With the two examples before, the 2.x code can now look like this since `ResourceSubscriber` implements `Disposable` directly: + +```java +CompositeDisposable composite2 = new CompositeDisposable(); + +composite2.add(Flowable.range(1, 5).subscribeWith(subscriber)); +``` + +### Calling request from onSubscribe/onStart + +Note that due to how request management works, calling `request(n)` from `Subscriber.onSubscribe` or `ResourceSubscriber.onStart` may trigger calls to `onNext` immediately before the `request()` call itself returns to the `onSubscribe`/`onStart` method of yours: + +```java +Flowable.range(1, 3).subscribe(new Subscriber<Integer>() { + + @Override + public void onSubscribe(Subscription s) { + System.out.println("OnSubscribe start"); + s.request(Long.MAX_VALUE); + System.out.println("OnSubscribe end"); + } + + @Override + public void onNext(Integer v) { + System.out.println(v); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onComplete() { + System.out.println("Done"); + } +}); +``` + +This will print: + +``` +OnSubscribe start +1 +2 +3 +Done +OnSubscribe end +``` + +The problem comes when one does some initialization in `onSubscribe`/`onStart` after calling `request` there and `onNext` may or may not see the effects of the initialization. To avoid this situation, make sure you call `request` **after** all initialization have been done in `onSubscribe`/`onStart`. + +This behavior differs from 1.x where a `request` call went through a deferred logic that accumulated requests until an upstream `Producer` arrived at some time (This nature adds overhead to all operators and consumers in 1.x.) In 2.x, there is always a `Subscription` coming down first and 90% of the time there is no need to defer requesting. + +# Subscription + +In RxJava 1.x, the interface `rx.Subscription` was responsible for stream and resource lifecycle management, namely unsubscribing a sequence and releasing general resources such as scheduled tasks. The Reactive Streams specification took this name for specifying an interaction point between a source and a consumer: `org.reactivestreams.Subscription` allows requesting a positive amount from the upstream and allows cancelling the sequence. + +To avoid the name clash, the 1.x `rx.Subscription` has been renamed into `io.reactivex.Disposable` (somewhat resembling .NET's own IDisposable). + +Because Reactive Streams base interface, `org.reactivestreams.Publisher` defines the `subscribe()` method as `void`, `Flowable.subscribe(Subscriber)` no longer returns any `Subscription` (or `Disposable`). The other base reactive types also follow this signature with their respective subscriber types. + +The other overloads of `subscribe` now return `Disposable` in 2.x. + +The original `Subscription` container types have been renamed and updated + + - `CompositeSubscription` to `CompositeDisposable` + - `SerialSubscription` and `MultipleAssignmentSubscription` have been merged into `SerialDisposable`. The `set()` method disposes the old value and `replace()` method does not. + - `RefCountSubscription` has been removed. + +# Backpressure + +The Reactive Streams specification mandates operators supporting backpressure, specifically via the guarantee that they don't overflow their consumers when those don't request. Operators of the new `Flowable` base reactive type now consider downstream request amounts properly, however, this doesn't mean `MissingBackpressureException` is gone. The exception is still there but this time, the operator that can't signal more `onNext` will signal this exception instead (allowing better identification of who is not properly backpressured). + +As an alternative, the 2.x `Observable` doesn't do backpressure at all and is available as a choice to switch over. + +# Reactive Streams compliance + +**updated in 2.0.7** + +**The `Flowable`-based sources and operators are, as of 2.0.7, fully Reactive Streams version 1.0.0 specification compliant.** + +Before 2.0.7, the operator `strict()` had to be applied in order to achieve the same level of compliance. In 2.0.7, the operator `strict()` returns `this`, is deprecated and will be removed completely in 2.1.0. + +As one of the primary goals of RxJava 2, the design focuses on performance and in order enable it, RxJava 2.0.7 adds a custom `io.reactivex.FlowableSubscriber` interface (extends `org.reactivestreams.Subscriber`) but adds no new methods to it. The new interface is **constrained to RxJava 2** and represents a consumer to `Flowable` that is able to work in a mode that relaxes the Reactive Streams version 1.0.0 specification in rules §1.3, §2.3, §2.12 and §3.9: + + - §1.3 relaxation: `onSubscribe` may run concurrently with `onNext` in case the `FlowableSubscriber` calls `request()` from inside `onSubscribe` and it is the resposibility of `FlowableSubscriber` to ensure thread-safety between the remaining instructions in `onSubscribe` and `onNext`. + - §2.3 relaxation: calling `Subscription.cancel` and `Subscription.request` from `FlowableSubscriber.onComplete()` or `FlowableSubscriber.onError()` is considered a no-operation. + - §2.12 relaxation: if the same `FlowableSubscriber` instance is subscribed to multiple sources, it must ensure its `onXXX` methods remain thread safe. + - §3.9 relaxation: issuing a non-positive `request()` will not stop the current stream but signal an error via `RxJavaPlugins.onError`. + +From a user's perspective, if one was using the the `subscribe` methods other than `Flowable.subscribe(Subscriber<? super T>)`, there is no need to do anything regarding this change and there is no extra penalty for it. + +If one was using `Flowable.subscribe(Subscriber<? super T>)` with the built-in RxJava `Subscriber` implementations such as `DisposableSubscriber`, `TestSubscriber` and `ResourceSubscriber`, there is a small runtime overhead (one `instanceof` check) associated when the code is not recompiled against 2.0.7. + +If a custom class implementing `Subscriber` was employed before, subscribing it to a `Flowable` adds an internal wrapper that ensures observing the Flowable is 100% compliant with the specification at the cost of some per-item overhead. + +In order to help lift these extra overheads, a new method `Flowable.subscribe(FlowableSubscriber<? super T>)` has been added which exposes the original behavior from before 2.0.7. It is recommended that new custom consumer implementations extend `FlowableSubscriber` instead of just `Subscriber`. + +# Runtime hooks + +The 2.x redesigned the `RxJavaPlugins` class which now supports changing the hooks at runtime. Tests that want to override the schedulers and the lifecycle of the base reactive types can do it on a case-by-case basis through callback functions. + +The class-based `RxJavaObservableHook` and friends are now gone and `RxJavaHooks` functionality is incorporated into `RxJavaPlugins`. + +# Error handling + +One important design requirement for 2.x is that no `Throwable` errors should be swallowed. This means errors that can't be emitted because the downstream's lifecycle already reached its terminal state or the downstream cancelled a sequence which was about to emit an error. + +Such errors are routed to the `RxJavaPlugins.onError` handler. This handler can be overridden with the method `RxJavaPlugins.setErrorHandler(Consumer<Throwable>)`. Without a specific handler, RxJava defaults to printing the `Throwable`'s stacktrace to the console and calls the current thread's uncaught exception handler. + +On desktop Java, this latter handler does nothing on an `ExecutorService` backed `Scheduler` and the application can keep running. However, Android is more strict and terminates the application in such uncaught exception cases. + +If this behavior is desirable can be debated, but in any case, if you want to avoid such calls to the uncaught exception handler, the **final application** that uses RxJava 2 (directly or transitively) should set a no-op handler: + +```java +// If Java 8 lambdas are supported +RxJavaPlugins.setErrorHandler(e -> { }); + +// If no Retrolambda or Jack +RxJavaPlugins.setErrorHandler(Functions.<Throwable>emptyConsumer()); +``` +It is not advised intermediate libraries change the error handler outside their own testing environment. + +Unfortunately, RxJava can't tell which of these out-of-lifecycle, undeliverable exceptions should or shouldn't crash your app. Identifying the source and reason for these exceptions can be tiresome, especially if they originate from a source and get routed to `RxJavaPlugins.onError` somewhere lower the chain. + +Therefore, 2.0.6 introduces specific exception wrappers to help distinguish and track down what was happening the time of the error: +- `OnErrorNotImplementedException`: reintroduced to detect when the user forgot to add error handling to `subscribe()`. +- `ProtocolViolationException`: indicates a bug in an operator +- `UndeliverableException`: wraps the original exception that can't be delivered due to lifecycle restrictions on a `Subscriber`/`Observer`. It is automatically applied by `RxJavaPlugins.onError` with intact stacktrace that may help find which exact operator rerouted the original error. + +If an undeliverable exception is an instance/descendant of `NullPointerException`, `IllegalStateException` (`UndeliverableException` and `ProtocolViolationException` extend this), `IllegalArgumentException`, `CompositeException`, `MissingBackpressureException` or `OnErrorNotImplementedException`, the `UndeliverableException` wrapping doesn't happen. + +In addition, some 3rd party libraries/code throw when they get interrupted by a cancel/dispose call which leads to an undeliverable exception most of the time. Internal changes in 2.0.6 now consistently cancel or dispose a `Subscription`/`Disposable` before cancelling/disposing a task or worker (which causes the interrupt on the target thread). + +``` java +// in some library +try { + doSomethingBlockingly() +} catch (InterruptedException ex) { + // check if the interrupt is due to cancellation + // if so, no need to signal the InterruptedException + if (!disposable.isDisposed()) { + observer.onError(ex); + } +} +``` + +If the library/code already did this, the undeliverable `InterruptedException`s should stop now. If this pattern was not employed before, we encourage updating the code/library in question. + +If one decides to add a non-empty global error consumer, here is an example that manages the typical undeliverable exceptions based on whether they represent a likely bug or an ignorable application/network state: + +```java +RxJavaPlugins.setErrorHandler(e -> { + if (e instanceof UndeliverableException) { + e = e.getCause(); + } + if ((e instanceof IOException) || (e instanceof SocketException)) { + // fine, irrelevant network problem or API that throws on cancellation + return; + } + if (e instanceof InterruptedException) { + // fine, some blocking code was interrupted by a dispose call + return; + } + if ((e instanceof NullPointerException) || (e instanceof IllegalArgumentException)) { + // that's likely a bug in the application + Thread.currentThread().getUncaughtExceptionHandler() + .handleException(Thread.currentThread(), e); + return; + } + if (e instanceof IllegalStateException) { + // that's a bug in RxJava or in a custom operator + Thread.currentThread().getUncaughtExceptionHandler() + .handleException(Thread.currentThread(), e); + return; + } + Log.warning("Undeliverable exception received, not sure what to do", e); +}); +``` + +# Schedulers + +The 2.x API still supports the main default scheduler types: `computation`, `io`, `newThread` and `trampoline`, accessible through `io.reactivex.schedulers.Schedulers` utility class. + +The `immediate` scheduler is not present in 2.x. It was frequently misused and didn't implement the `Scheduler` specification correctly anyway; it contained blocking sleep for delayed action and didn't support recursive scheduling at all. Use `Schedulers.trampoline()` instead. + +The `Schedulers.test()` has been removed as well to avoid the conceptional difference with the rest of the default schedulers. Those return a "global" scheduler instance whereas `test()` returned always a new instance of the `TestScheduler`. Test developers are now encouraged to simply `new TestScheduler()` in their code. + +The `io.reactivex.Scheduler` abstract base class now supports scheduling tasks directly without the need to create and then destroy a `Worker` (which is often forgotten): + +```java +public abstract class Scheduler { + + public Disposable scheduleDirect(Runnable task) { ... } + + public Disposable scheduleDirect(Runnable task, long delay, TimeUnit unit) { ... } + + public Disposable scheduleDirectPeriodically(Runnable task, long initialDelay, + long period, TimeUnit unit) { ... } + + public long now(TimeUnit unit) { ... } + + // ... rest is the same: lifecycle methods, worker creation +} +``` + +The main purpose is to avoid the tracking overhead of the `Worker`s for typically one-shot tasks. The methods have a default implementation that reuses `createWorker` properly but can be overridden with more efficient implementations if necessary. + +The method that returns the scheduler's own notion of current time, `now()` has been changed to accept a `TimeUnit` to indicate the unit of measure. + +# Entering the reactive world + +One of the design flaws of RxJava 1.x was the exposure of the `rx.Observable.create()` method that while powerful, not the typical operator you want to use to enter the reactive world. Unfortunately, so many depend on it that we couldn't remove or rename it. + +Since 2.x is a fresh start, we won't make that mistake again. Each reactive base type `Flowable`, `Observable`, `Single`, `Maybe` and `Completable` feature a safe `create` operator that does the right thing regarding backpressure (for `Flowable`) and cancellation (all): + +```java +Flowable.create((FlowableEmitter<Integer> emitter) -> { + emitter.onNext(1); + emitter.onNext(2); + emitter.onComplete(); +}, BackpressureStrategy.BUFFER); +``` + +Practically, the 1.x `fromEmitter` (formerly `fromAsync`) has been renamed to `Flowable.create`. The other base reactive types have similar `create` methods (minus the backpressure strategy). + +# Leaving the reactive world + +Apart from subscribing to the base types with their respective consumers (`Subscriber`, `Observer`, `SingleObserver`, `MaybeObserver` and `CompletableObserver`) and functional-interface based consumers (such as `subscribe(Consumer<T>, Consumer<Throwable>, Action)`), the formerly separate 1.x `BlockingObservable` (and similar classes for the others) has been integrated with the main reactive type. Now you can directly block for some results by invoking a `blockingX` operation directly: + +```java +List<Integer> list = Flowable.range(1, 100).toList().blockingGet(); // toList() returns Single + +Integer i = Flowable.range(100, 100).blockingLast(); +``` + +(The reason for this is twofold: performance and ease of use of the library as a synchronous Java 8 Streams-like processor.) + +Another significant difference between `rx.Subscriber` (and co) and `org.reactivestreams.Subscriber` (and co) is that in 2.x, your `Subscriber`s and `Observer`s are not allowed to throw anything but fatal exceptions (see `Exceptions.throwIfFatal()`). (The Reactive Streams specification allows throwing `NullPointerException` if the `onSubscribe`, `onNext` or `onError` receives a `null` value, but RxJava doesn't let `null`s in any way.) This means the following code is no longer legal: + +```java +Subscriber<Integer> subscriber = new Subscriber<Integer>() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + public void onNext(Integer t) { + if (t == 1) { + throw new IllegalArgumentException(); + } + } + + public void onError(Throwable e) { + if (e instanceof IllegalArgumentException) { + throw new UnsupportedOperationException(); + } + } + + public void onComplete() { + throw new NoSuchElementException(); + } +}; + +Flowable.just(1).subscribe(subscriber); +``` + +The same applies to `Observer`, `SingleObserver`, `MaybeObserver` and `CompletableObserver`. + +Since many of the existing code targeting 1.x do such things, the method `safeSubscribe` has been introduced that does handle these non-conforming consumers. + +Alternatively, you can use the `subscribe(Consumer<T>, Consumer<Throwable>, Action)` (and similar) methods to provide a callback/lambda that can throw: + +```java +Flowable.just(1) +.subscribe( + subscriber::onNext, + subscriber::onError, + subscriber::onComplete, + subscriber::onSubscribe +); +``` + +# Testing + +Testing RxJava 2.x works the same way as it does in 1.x. `Flowable` can be tested with `io.reactivex.subscribers.TestSubscriber` whereas the non-backpressured `Observable`, `Single`, `Maybe` and `Completable` can be tested with `io.reactivex.observers.TestObserver`. + +## test() "operator" + +To support our internal testing, all base reactive types now feature `test()` methods (which is a huge convenience for us) returning `TestSubscriber` or `TestObserver`: + +```java +TestSubscriber<Integer> ts = Flowable.range(1, 5).test(); + +TestObserver<Integer> to = Observable.range(1, 5).test(); + +TestObserver<Integer> tso = Single.just(1).test(); + +TestObserver<Integer> tmo = Maybe.just(1).test(); + +TestObserver<Integer> tco = Completable.complete().test(); +``` + +The second convenience is that most `TestSubscriber`/`TestObserver` methods return the instance itself allowing chaining the various `assertX` methods. The third convenience is that you can now fluently test your sources without the need to create or introduce `TestSubscriber`/`TestObserver` instance in your code: + +```java +Flowable.range(1, 5) +.test() +.assertResult(1, 2, 3, 4, 5) +; +``` + +### Notable new assert methods + + - `assertResult(T... items)`: asserts if subscribed, received exactly the given items in the given order followed by `onComplete` and no errors + - `assertFailure(Class<? extends Throwable> clazz, T... items)`: asserts if subscribed, received exactly the given items in the given order followed by a `Throwable` error of wich `clazz.isInstance()` returns true. + - `assertFailureAndMessage(Class<? extends Throwable> clazz, String message, T... items)`: same as `assertFailure` plus validates the `getMessage()` contains the specified message + - `awaitDone(long time, TimeUnit unit)` awaits a terminal event (blockingly) and cancels the sequence if the timeout elapsed. + - `assertOf(Consumer<TestSubscriber<T>> consumer)` compose some assertions into the fluent chain (used internally for fusion test as operator fusion is not part of the public API right now). + +One of the benefits is that changing `Flowable` to `Observable` here the test code part doesn't have to change at all due to the implicit type change of the `TestSubscriber` to `TestObserver`. + +## cancel and request upfront + +The `test()` method on `TestObserver` has a `test(boolean cancel)` overload which cancels/disposes the `TestSubscriber`/`TestObserver` before it even gets subscribed: + +```java +PublishSubject<Integer> pp = PublishSubject.create(); + +// nobody subscribed yet +assertFalse(pp.hasSubscribers()); + +pp.test(true); + +// nobody remained subscribed +assertFalse(pp.hasSubscribers()); +``` + +`TestSubscriber` has the `test(long initialRequest)` and `test(long initialRequest, boolean cancel)` overloads to specify the initial request amount and whether the `TestSubscriber` should be also immediately cancelled. If the `initialRequest` is given, the `TestSubscriber` offers the `requestMore(long)` method to keep requesting in a fluent manner: + +```java +Flowable.range(1, 5) +.test(0) +.assertValues() +.requestMore(1) +.assertValues(1) +.requestMore(2) +.assertValues(1, 2, 3) +.requestMore(2) +.assertResult(1, 2, 3, 4, 5); +``` + +or alternatively the `TestSubscriber` instance has to be captured to gain access to its `request()` method: + +```java +PublishProcessor<Integer> pp = PublishProcessor.create(); + +TestSubscriber<Integer> ts = pp.test(0L); + +ts.request(1); + +pp.onNext(1); +pp.onNext(2); + +ts.assertFailure(MissingBackpressureException.class, 1); +``` + +## Testing an async source + +Given an asynchronous source, fluent blocking for a terminal event is now possible: + +```java +Flowable.just(1) +.subscribeOn(Schedulers.single()) +.test() +.awaitDone(5, TimeUnit.SECONDS) +.assertResult(1); +``` + +## Mockito & TestSubscriber + +Those who are using Mockito and mocked `Observer` in 1.x has to mock the `Subscriber.onSubscribe` method to issue an initial request, otherwise, the sequence will hang or fail with hot sources: + +```java +@SuppressWarnings("unchecked") +public static <T> Subscriber<T> mockSubscriber() { + Subscriber<T> w = mock(Subscriber.class); + + Mockito.doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock a) throws Throwable { + Subscription s = a.getArgumentAt(0, Subscription.class); + s.request(Long.MAX_VALUE); + return null; + } + }).when(w).onSubscribe((Subscription)any()); + + return w; +} +``` + +# Operator differences + +Most operators are still there in 2.x and practically all of them have the same behavior as they had in 1.x. The following subsections list each base reactive type and the difference between 1.x and 2.x. + +Generally, many operators gained overloads that now allow specifying the internal buffer size or prefetch amount they should run their upstream (or inner sources). + +Some operator overloads have been renamed with a postfix, such as `fromArray`, `fromIterable` etc. The reason for this is that when the library is compiled with Java 8, the javac often can't disambiguate between functional interface types. + +Operators marked as `@Beta` or `@Experimental` in 1.x are promoted to standard. + +## 1.x Observable to 2.x Flowable + +### Factory methods: + +| 1.x | 2.x | +|----------|-----------| +| `amb` | added `amb(ObservableSource...)` overload, 2-9 argument versions dropped | +| RxRingBuffer.SIZE | `bufferSize()` | +| `combineLatest` | added varargs overload, added overloads with `bufferSize` argument, `combineLatest(List)` dropped | +| `concat` | added overload with `prefetch` argument, 5-9 source overloads dropped, use `concatArray` instead | +| N/A | added `concatArray` and `concatArrayDelayError` | +| N/A | added `concatArrayEager` and `concatArrayEagerDelayError` | +| `concatDelayError` | added overloads with option to delay till the current ends or till the very end | +| `concatEagerDelayError` | added overloads with option to delay till the current ends or till the very end | +| `create(SyncOnSubscribe)` | replaced with `generate` + overloads (distinct interfaces, you can implement them all at once) | +| `create(AsnycOnSubscribe)` | not present | +| `create(OnSubscribe)` | repurposed with safe `create(FlowableOnSubscribe, BackpressureStrategy)`, raw support via `unsafeCreate()` | +| `from` | disambiguated into `fromArray`, `fromIterable`, `fromFuture` | +| N/A | added `fromPublisher` | +| `fromAsync` | renamed to `create()` | +| N/A | added `intervalRange()` | +| `limit` | dropped, use `take` | +| `merge` | added overloads with `prefetch` | +| `mergeDelayError` | added overloads with `prefetch` | +| `sequenceEqual` | added overload with `bufferSize` | +| `switchOnNext` | added overload with `prefetch` | +| `switchOnNextDelayError` | added overload with `prefetch` | +| `timer` | deprecated overloads dropped | +| `zip` | added overloads with `bufferSize` and `delayErrors` capabilities, disambiguated to `zipArray` and `zipIterable` | + +### Instance methods: + +| 1.x | 2.x | +|----------|----------| +| `all` | **RC3** returns `Single<Boolean>` now | +| `any` | **RC3** returns `Single<Boolean>` now | +| `asObservable` | renamed to `hide()`, hides all identities now | +| `buffer` | overloads with custom `Collection` supplier | +| `cache(int)` | deprecated and dropped | +| `collect` | **RC3** returns `Single<U>` | +| `collect(U, Action2<U, T>)` | disambiguated to `collectInto` and **RC3** returns `Single<U>` | +| `concatMap` | added overloads with `prefetch` | +| `concatMapDelayError` | added overloads with `prefetch`, option to delay till the current ends or till the very end | +| `concatMapEager` | added overloads with `prefetch` | +| `concatMapEagerDelayError` | added overloads with `prefetch`, option to delay till the current ends or till the very end | `contains` | **RC3** returns `Single<Boolean> now | +| `count` | **RC3** returns `Single<Long>` now | +| `countLong` | dropped, use `count` | +| `distinct` | overload with custom `Collection` supplier. | +| `doOnCompleted` | renamed to `doOnComplete`, note the missing `d`! | +| `doOnUnsubscribe` | renamed to `Flowable.doOnCancel` and `doOnDispose` for the others, [additional info](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#dooncanceldoondisposeunsubscribeon) | +| N/A | added `doOnLifecylce` to handle `onSubscribe`, `request` and `cancel` peeking | +| `elementAt(int)` | **RC3** no longer signals NoSuchElementException if the source is shorter than the index | +| `elementAt(Func1, int)` | dropped, use `filter(predicate).elementAt(int)` | +| `elementAtOrDefault(int, T)` | renamed to `elementAt(int, T)` and **RC3** returns `Single<T>` | +| `elementAtOrDefault(Func1, int, T)` | dropped, use `filter(predicate).elementAt(int, T)` | +| `first()` | **RC3** renamed to `firstElement` and returns `Maybe<T>` | +| `first(Func1)` | dropped, use `filter(predicate).first()` | +| `firstOrDefault(T)` | renamed to `first(T)` and **RC3** returns `Single<T>` | +| `firstOrDefault(Func1, T)` | dropped, use `filter(predicate).first(T)` | +| `flatMap` | added overloads with `prefetch` | +| N/A | added `forEachWhile(Predicate<T>, [Consumer<Throwable>, [Action]])` for conditionally stopping consumption | +| `groupBy` | added overload with `bufferSize` and `delayError` option, *the custom internal map version didn't make it into RC1* | +| `ignoreElements` | **RC3** returns `Completable` | +| `isEmpty` | **RC3** returns `Single<Boolean>` | +| `last()` | **RC3** renamed to `lastElement` and returns `Maybe<T>` | +| `last(Func1)` | dropped, use `filter(predicate).last()` | +| `lastOrDefault(T)` | renamed to `last(T)` and **RC3** returns `Single<T>` | +| `lastOrDefault(Func1, T)` | dropped, use `filter(predicate).last(T)` | +| `nest` | dropped, use manual `just` | +| `publish(Func1)` | added overload with `prefetch` | +| `reduce(Func2)` | **RC3** returns `Maybe<T>` | +| N/A | added `reduceWith(Callable, BiFunction)` to reduce in a Subscriber-individual manner, returns `Single<T>` | +| N/A | added `repeatUntil(BooleanSupplier)` | +| `repeatWhen(Func1, Scheduler)` | dropped the overload, use `subscribeOn(Scheduler).repeatWhen(Function)` instead | +| `retry` | added `retry(Predicate)`, `retry(int, Predicate)` | +| N/A | added `retryUntil(BooleanSupplier)` | +| `retryWhen(Func1, Scheduler)` | dropped the overload, use `subscribeOn(Scheduler).retryWhen(Function)` instead | +| `sample` | doesn't emit the very last item if the upstream completes within the period, added overloads with `emitLast` parameter | +| N/A | added `scanWith(Callable, BiFunction)` to scan in a Subscriber-individual manner | +| `single()` | **RC3** renamed to `singleElement` and returns `Maybe<T>` | +| `single(Func1)` | dropped, use `filter(predicate).single()` | +| `singleOrDefault(T)` | renamed to `single(T)` and **RC3** returns `Single<T>` | +| `singleOrDefault(Func1, T)` | dropped, use `filter(predicate).single(T)` | +| `skipLast` | added overloads with `bufferSize` and `delayError` options | +| `startWith` | 2-9 argument version dropped, use `startWithArray` instead | +| N/A | added `startWithArray` to disambiguate | +| `subscribe` | No longer wraps all consumer types (i.e., `Observer`) with a safety wrapper, (just like the 1.x `unsafeSubscribe` no longer available). Use `safeSubscribe` to get an explicit safety wrapper around a consumer type. | +| N/A | added `subscribeWith` that returns its input after subscription | +| `switchMap` | added overload with `prefetch` argument | +| `switchMapDelayError` | added overload with `prefetch` argument | +| `takeLastBuffer` | dropped | +| N/A | added `test()` (returns TestSubscriber subscribed to this) with overloads to fluently test | +| `throttleLast` | doesn't emit the very last item if the upstream completes within the period, use `sample` with the `emitLast` parameter | +| `timeout(Func0<Observable>, ...)` | signature changed to `timeout(Publisher, ...)` and dropped the function, use `defer(Callable<Publisher>>)` if necessary | +| `toBlocking().y` | inlined as `blockingY()` operators, except `toFuture` | +| `toCompletable` | **RC3** dropped, use `ignoreElements` | +| `toList` | **RC3** returns `Single<List<T>>` | +| `toMap` | **RC3** returns `Single<Map<K, V>>` | +| `toMultimap` | **RC3** returns `Single<Map<K, Collection<V>>>` | +| N/A | added `toFuture` | +| N/A | added `toObservable` | +| `toSingle` | **RC3** dropped, use `single(T)` | +| `toSortedList` | **RC3** returns `Single<List<T>>` | +| `unsafeSubscribe` | Removed as the Reactive Streams specification mandates the `onXXX` methods don't crash and therefore the default is to not have a safety net in `subscribe`. The new `safeSubscribe` method was introduced to explicitly add the safety wrapper around a consumer type. | +| `withLatestFrom` | 5-9 source overloads dropped | +| `zipWith` | added overloads with `prefetch` and `delayErrors` options | + +### Different return types + +Some operators that produced exactly one value or an error now return `Single` in 2.x (or `Maybe` if an empty source is allowed). + +*(Remark: this is "experimental" in RC2 and RC3 to see how it feels to program with such mixed-type sequences and whether or not there has to be too much `toObservable`/`toFlowable` back-conversion.)* + +| Operator | Old return type | New return type | Remark | +|----------|-----------------|-----------------|--------| +| `all(Predicate)` | `Observable<Boolean>` | `Single<Boolean>` | Emits true if all elements match the predicate | +| `any(Predicate)` | `Observable<Boolean>` | `Single<Boolean>` | Emits true if any elements match the predicate | +| `count()` | `Observable<Long>` | `Single<Long>` | Counts the number of elements in the sequence | +| `elementAt(int)` | `Observable<T>` | `Maybe<T>` | Emits the element at the given index or completes | +| `elementAt(int, T)` | `Observable<T>` | `Single<T>` | Emits the element at the given index or the default | +| `elementAtOrError(int)` | `Observable<T>` | `Single<T>` | Emits the indexth element or a `NoSuchElementException` | +| `first(T)` | `Observable<T>` | `Single<T>` | Emits the very first element or `NoSuchElementException` | +| `firstElement()` | `Observable<T>` | `Maybe<T>` | Emits the very first element or completes | +| `firstOrError()` | `Observable<T>` | `Single<T>` | Emits the first element or a `NoSuchElementException` if the source is empty | +| `ignoreElements()` | `Observable<T>` | `Completable` | Ignore all but the terminal events | +| `isEmpty()` | `Observable<Boolean>` | `Single<Boolean>` | Emits true if the source is empty | +| `last(T)` | `Observable<T>` | `Single<T>` | Emits the very last element or the default item | +| `lastElement()` | `Observable<T>` | `Maybe<T>` | Emits the very last element or completes | +| `lastOrError()` | `Observable<T>` | `Single<T>` | Emits the lastelement or a `NoSuchElementException` if the source is empty | +| `reduce(BiFunction)` | `Observable<T>` | `Maybe<T>` | Emits the reduced value or completes | +| `reduce(Callable, BiFunction)` | `Observable<U>` | `Single<U>` | Emits the reduced value (or the initial value) | +| `reduceWith(U, BiFunction)` | `Observable<U>` | `Single<U>` | Emits the reduced value (or the initial value) | +| `single(T)` | `Observable<T>` | `Single<T>` | Emits the only element or the default item | +| `singleElement()` | `Observable<T>` | `Maybe<T>` | Emits the only element or completes | +| `singleOrError()` | `Observable<T>` | `Single<T>` | Emits the one and only element, IndexOutOfBoundsException if the source is longer than 1 item or a `NoSuchElementException` if the source is empty | +| `toList()` | `Observable<List<T>>` | `Single<List<T>>` | collects all elements into a `List` | +| `toMap()` | `Observable<Map<K, V>>` | `Single<Map<K, V>>` | collects all elements into a `Map` | +| `toMultimap()` | `Observable<Map<K, Collection<V>>>` | `Single<Map<K, Collection<V>>>` | collects all elements into a `Map` with collection | +| `toSortedList()` | `Observable<List<T>>` | `Single<List<T>>` | collects all elements into a `List` and sorts it | + +### Removals + +To make sure the final API of 2.0 is clean as possible, we remove methods and other components between release candidates without deprecating them. + +| Removed in version | Component | Remark | +|---------|-----------|--------| +| RC3 | `Flowable.toCompletable()` | use `Flowable.ignoreElements()` | +| RC3 | `Flowable.toSingle()` | use `Flowable.single(T)` | +| RC3 | `Flowable.toMaybe()` | use `Flowable.singleElement()` | +| RC3 | `Observable.toCompletable()` | use `Observable.ignoreElements()` | +| RC3 | `Observable.toSingle()` | use `Observable.single(T)` | +| RC3 | `Observable.toMaybe()` | use `Observable.singleElement()` | + +# Miscellaneous changes + +## doOnCancel/doOnDispose/unsubscribeOn + +In 1.x, the `doOnUnsubscribe` was always executed on a terminal event because 1.x' `SafeSubscriber` called `unsubscribe` on itself. This was practically unnecessary and the Reactive Streams specification states that when a terminal event arrives at a `Subscriber`, the upstream `Subscription` should be considered cancelled and thus calling `cancel()` is a no-op. + +For the same reason, `unsubscribeOn` is not called on the regular termination path but only when there is an actual `cancel` (or `dispose`) call on the chain. + +Therefore, the following sequence won't call `doOnCancel`: + +```java +Flowable.just(1, 2, 3) +.doOnCancel(() -> System.out.println("Cancelled!")) +.subscribe(System.out::println); +``` + +However, the following will call since the `take` operator cancels after the set amount of `onNext` events have been delivered: + +```java +Flowable.just(1, 2, 3) +.doOnCancel(() -> System.out.println("Cancelled!")) +.take(2) +.subscribe(System.out::println); +``` + +If you need to perform cleanup on both regular termination or cancellation, consider the operator `using` instead. + +Alternatively, the `doFinally` operator (introduced in 2.0.1 and standardized in 2.1) calls a developer specified `Action` that gets executed after a source completed, failed with an error or got cancelled/disposed: + +```java +Flowable.just(1, 2, 3) +.doFinally(() -> System.out.println("Finally")) +.subscribe(System.out::println); + +Flowable.just(1, 2, 3) +.doFinally(() -> System.out.println("Finally")) +.take(2) // cancels the above after 2 elements +.subscribe(System.out::println); +``` diff --git a/docs/What's-different-in-3.0.md b/docs/What's-different-in-3.0.md new file mode 100644 index 0000000000..e936692c06 --- /dev/null +++ b/docs/What's-different-in-3.0.md @@ -0,0 +1,33 @@ +Table of contents + +# Introduction +TBD. + +### API signature changes + +TBD. + +- as() merged into to() +- some operators returning a more appropriate Single or Maybe +- functional interfaces throws widening to Throwable +- standard methods removed +- standard methods signature changes + +### Standardized operators + +(former experimental and beta operators from 2.x) + +TBD. + +### Operator behavior changes + +TBD. + +- connectable sources lifecycle-fixes + + +### Test support changes + +TBD. + +- methods removed from the test consumers diff --git a/docs/Writing-operators-for-2.0.md b/docs/Writing-operators-for-2.0.md new file mode 100644 index 0000000000..9649c02be7 --- /dev/null +++ b/docs/Writing-operators-for-2.0.md @@ -0,0 +1,1746 @@ +##### Table of contents + + - [Introduction](#introduction) + - [Warning on internal components](warning-on-internal-components) + - [Atomics, serialization, deferred actions](#atomics-serialization-deferred-actions) + - [Field updaters and Android](#field-updaters-and-android) + - [Request accounting](#request-accounting) + - [Once](#once) + - [Serialization](#serialization) + - [Queues](#queues) + - [Deferred actions](#deferred-actions) + - [Deferred cancellation](#deferred-cancellation) + - [Deferred requesting](#deferred-requesting) + - [Atomic error management](#atomic-error-management) + - [Half-serialization](#half-serialization) + - [Fast-path queue-drain](#fast-path-queue-drain) + - [FlowableSubscriber](#flowablesubscriber) + - [Backpressure and cancellation](#backpressure-and-cancellation) + - [Replenishing](#replenishing) + - [Stable prefetching](#stable-prefetching) + - [Single-valued results](#single-valued-results) + - [Single-element post-complete](#single-element-post-complete) + - [Multi-element post-complete](#multi-element-post-complete) + - [Creating operator classes](#creating-operator-classes) + - [Operator by extending a base reactive class](#operator-by-extending-a-base-reactive-class) + - [Operator targeting lift()](#operator-targeting-lift) + - [Operator fusion](#operator-fusion) + - [Generations](#generations) + - [Components](#components) + - [Callable and ScalarCallable](#callable-and-scalarcallable) + - [ConditionalSubscriber](#conditionalsubscriber) + - [QueueSubscription and QueueDisposable](#queuesubscription-and-queuedisposable) + - [Example implementations](#example-implementations) + - [Map-filter hybrid](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#map--filter-hybrid) + - [Ordered merge](#ordered-merge) + +# Introduction + +Writing operators, source-like (`fromEmitter`) or intermediate-like (`flatMap`) **has always been a hard task to do in RxJava**. There are many rules to obey, many cases to consider but at the same time, many (legal) shortcuts to take to build a well performing code. Now writing an operator specifically for 2.x is 10 times harder than for 1.x. If you want to exploit all the advanced, 4th generation features, that's even 2-3 times harder on top (so 30 times harder in total). + +*(If you have been following [my blog](http://akarnokd.blogspot.hu/) about RxJava internals, writing operators is maybe only 2 times harder than 1.x; some things have moved around, some tools popped up while others have been dropped but there is a relatively straight mapping from 1.x concepts and approaches to 2.x concepts and approaches.)* + +In this article, I'll describe the how-to's from the perspective of a developer who skipped the 1.x knowledge base and basically wants to write operators that conforms the Reactive Streams specification as well as RxJava 2.x's own extensions and additional expectations/requirements. + +Since **Reactor 3** has the same architecture as **RxJava 2** (no accident, I architected and contributed 80% of **Reactor 3** as well) the same principles outlined in this page applies to writing operators for **Reactor 3**. Note however that they chose different naming and locations for their utility and support classes so you may have to search for the equivalent components. + +## Warning on internal components + +RxJava has several hundred public classes implementing various operators and helper facilities. Since there is no way to hide these in Java 6-8, the general contract is that anything below `io.reactivex.internal` is considered private and subject to change without warnings. It is not recommended to reference these in your code (unless you contribute to RxJava itself) and must be prepared that even a patch change may shuffle/rename things around in them. That being said, they usually contain valuable tools for operator builders and as such are quite attractive to use them in your custom code. + +# Atomics, serialization, deferred actions + +As RxJava itself has building blocks for creating reactive dataflows, its components have building blocks as well in the form of concurrency primitives and algorithms. Many refer to the book [Concurrency in Practice](https://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601) for learning the fundamentals needed. Unfortunately, other than some explanation of the Java Memory Model, the book lacks the techniques required for developing operators for RxJava 1.x and 2.x. + +## Field updaters and Android + +If you looked at the source code of RxJava and then at **Reactor 3**, you might have noticed that RxJava doesn't use the +`AtomicXFieldUpdater` classes. The reason for this is that on certain Android devices, the runtime "messes up" the field +names and the reflection logic behind the field updaters fails to locate those fields in the operators. To avoid this we decided to only use the full `AtomicX` classes (as fields or extending them). + +If you target the RxJava library with your custom operator (or Android), you are encouraged to do the same. If you plan have operators running on desktop Java, feel free to use the field updaters instead. + +## Request accounting + +When dealing with backpressure in `Flowable` operators, one needs a way to account the downstream requests and emissions in response to those requests. For this we use a single `AtomicLong`. Accounting must be atomic because requesting more and emitting items to fulfill an earlier request may happen at the same time. + +The naive approach for accounting would be to simply call `AtomicLong.getAndAdd()` with new requests and `AtomicLong.addAndGet()` for decrementing based on how many elements were emitted. + +The problem with this is that the Reactive Streams specification declares `Long.MAX_VALUE` as the upper bound for outstanding requests (interprets it as the unbounded mode) but adding two large longs may overflow the `long` into a negative value. In addition, if for some reason, there are more values emitted than were requested, the subtraction may yield a negative current request value, causing crashes or hangs. + +Therefore, both addition and subtraction have to be capped at `Long.MAX_VALUE` and `0` respectively. Since there is no dedicated `AtomicLong` method for it, we have to use a Compare-And-Set loop. (Usually, requesting happens relatively rarely compared to emission amounts thus the lack of dedicated machine code instruction is not a performance bottleneck.) + +```java +public static long add(AtomicLong requested, long n) { + for (;;) { + long current = requested.get(); + if (current == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long update = current + n; + if (update < 0L) { + update = Long.MAX_VALUE; + } + if (requested.compareAndSet(current, update)) { + return current; + } + } +} + +public static long produced(AtomicLong requested, long n) { +for (;;) { + long current = requested.get(); + if (current == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long update = current - n; + if (update < 0L) { + update = 0; + } + if (requested.compareAndSet(current, update)) { + return update; + } + } +} +``` + +In fact, these are so common in RxJava's operators that these algorithms are available as utility methods on the **internal** `BackpressureHelper` class under the same name. + +Sometimes, instead of having a separate `AtomicLong` field, your operator can extend `AtomicLong` saving on the indirection and class size. The practice in RxJava 2.x operators is that unless there is another atomic counter needed by the operator, (such as work-in-progress counter, see the later subsection) and otherwise doesn't need a base class, they extend `AtomicLong` directly. + +The `BackpressureHelper` class features special versions of `add` and `produced` which treat `Long.MIN_VALUE` as a cancellation indication and won't change the `AtomicLong`s value if they see it. + +## Once + +RxJava 2 expanded the single-event reactive types to include `Maybe` (called as the reactive `Optional` by some). The common property of `Single`, `Completable` and `Maybe` is that they can only call one of the 3 kinds of methods on their consumers: `(onSuccess | onError | onComplete)`. Since they also participate in concurrent scenarios, an operator needs a way to ensure that only one of them is called even though the input sources may call multiple of them at once. + +To ensure this, operators may use the `AtomicBoolean.compareAndSet` to atomically chose the event to relay (and thus the other events to drop). + +```java +final AtomicBoolean once = new AtomicBoolean(); + +final MaybeObserver<? super T> child = ...; + +void emitSuccess(T value) { + if (once.compareAndSet(false, true)) { + child.onSuccess(value); + } +} + +void emitFailure(Throwable e) { + if (once.compareAndSet(false, true)) { + child.onError(e); + } else { + RxJavaPlugins.onError(e); + } +} +``` + +Note that the same sequential requirement applies to these 0-1 reactive sources as to `Flowable`/`Observable`, therefore, if your operator doesn't have to deal with events from multiple sources (and pick one of them), you don't need this construct. + +## Serialization + +With more complicated sources, it may happen that multiple things happen that may trigger emission towards the downstream, such as upstream becoming available while the downstream requests for more data while the sequence gets cancelled by a timeout. + +Instead of working out the often very complicated state transitions via atomics, perhaps the easiest way is to serialize the events, actions or tasks and have one thread perform the necessary steps after that. This is what I call **queue-drain** approach (or trampolining by some). + +(The other approach, **emitter-loop** is no longer recommended with 2.x due to its potential blocking `synchronized` constructs that looks performant in single-threaded case but destroys it in true concurrent case.) + + +The concept is relatively simple: have a concurrent queue and a work-in-progress atomic counter, enqueue the item, increment the counter and if the counter transitioned from 0 to 1, keep draining the queue, work with the element and decrement the counter until it reaches zero again: + +```java +final ConcurrentLinkedQueue<Runnable> queue = ...; +final AtomicInteger wip = ...; + +void execute(Runnable r) { + queue.offer(r); + if (wip.getAndIncrement() == 0) { + do { + queue.poll().run(); + } while (wip.decrementAndGet() != 0); + } +} +``` + +The same pattern applies when one has to emit onNext values to a downstream consumer: + +```java +final ConcurrentLinkedQueue<T> queue = ...; +final AtomicInteger wip = ...; +final Subscriber<? super T> child = ...; + +void emit(T r) { + queue.offer(r); + if (wip.getAndIncrement() == 0) { + do { + child.onNext(queue.poll()); + } while (wip.decrementAndGet() != 0); + } +} +``` + +### Queues + +Using `ConcurrentLinkedQueue` is a reliable although mostly an overkill for such situations because it allocates on each call to `offer()` and is unbounded. It can be replaced with more optimized queues (see [JCTools](https://github.com/JCTools/JCTools/)) and RxJava itself also has some customized queues available (internal!): + + - `SpscArrayQueue` used when the queue is known to be fed by a single thread but the serialization has to look at other things (request, cancellation, termination) that can be read from other fields. Example: `observeOn` has a fixed request pattern which fits into this type of queue and extra fields for passing an error, completion or downstream requests into the drain logic. + - `SpscLinkedArrayQueue` used when the queue is known to be fed by a single thread but there is no bound on the element count. Example: `UnicastProcessor`, almost all buffering `Observable` operator. Some operators use it with multiple event sources by synchronizing on the `offer` side - a tradeoff between allocation and potential blocking: + +```java +SpscLinkedArrayQueue<T> q = ... +synchronized(q) { + q.offer(value); +} +``` + + - `MpscLinkedQueue` where there could be many feeders and unknown number of elements. Example: `buffer` with reactive boundary. + +The RxJava 2.x implementations of these types of queues have different class hierarchy than the JDK/JCTools versions. Our classes don't implement the `java.util.Queue` interface but rather a custom, simplified interface: + +```java +interface SimpleQueue<T> { + boolean offer(T t); + + boolean offer(T t1, T t2); + + T poll() throws Exception; + + boolean isEmpty(); + + void clear(); +} + +interface SimplePlainQueue<T> extends SimpleQueue<T> { + @Override + T poll(); +} + +public final class SpscArrayQueue<T> implements SimplePlainQueue<T> { + // ... +} +``` + +This simplified queue API gets rid of the unused parts (iterator, collections API remnants) and adds a bi-offer method (only implemented atomically in `SpscLinkedArrayQueue` currently). The second interface, `SimplePlainQueue` is defined to suppress the `throws Exception` on poll on queue types that won't throw that exception and there is no need for try-catch around them. + +## Deferred actions + +The Reactive Streams has a strict requirement that calling `onSubscribe()` must happen before any calls to the rest of the `onXXX` methods and by nature, any calls to `Subscription.request()` and `Subscription.cancel()`. The same logic applies to the design of `Observable`, `Single`, `Completable` and `Maybe` with their connection type of `Disposable`. + +Often though, such call to `onSubscribe` may happen later than the respective `cancel()` needs to happen. For example, the user may want to call `cancel()` before the respective `Subscription` actually becomes available in `subscribeOn`. Other operators may need to call `onSubscribe` before they connect to other sources but at that time, there is no direct way for relaying a `cancel` call to an unavailable upstream `Subscription`. + +The solution is **deferred cancellation** and **deferred requesting** in general. + +### Deferred cancellation + +This approach affects all 5 reactive types and works the same way for everyone. First, have an `AtomicReference` that will hold the respective connection type (or any other type whose method call has to happen later). Two methods are needed handling the `AtomicReference` class, one that sets the actual instance and one that calls the `cancel`/`dispose` method on it. + +```java +static final Disposable DISPOSED; +static { + DISPOSED = Disposables.empty(); + DISPOSED.dispose(); +} + +static boolean set(AtomicReference<Disposable> target, Disposable value) { + for (;;) { + Disposable current = target.get(); + if (current == DISPOSED) { + if (value != null) { + value.dispose(); + } + return false; + } + if (target.compareAndSet(current, value)) { + if (current != null) { + current.dispose(); + } + return true; + } + } +} + +static boolean dispose(AtomicReference<Disposable> target) { + Disposable current = target.getAndSet(DISPOSED); + if (current != DISPOSED) { + if (current != null) { + current.dispose(); + } + return true; + } + return false; +} +``` + +The approach uses an unique sentinel value `DISPOSED` - that should not appear elsewhere in your code - to indicate once a late actual `Disposable` arrives, it should be disposed immediately. Both methods return true if the operation succeeded or false if the target was already disposed. + +Sometimes, only one call to `set` is permitted (i.e., `setOnce`) and other times, the previous non-null value needs no call to `dispose` because it is known to be disposed already (i.e., `replace`). + +As with the request management, there are utility classes and methods for these operations: + + - (internal) `SequentialDisposable` that uses `update`, `replace` and `dispose` but leaks the API of `AtomicReference` + - `SerialDisposable` that has safe API with `set`, `replace` and `dispose` among other things + - (internal) `DisposableHelper` that features the methods shown above and the global disposed sentinel used by RxJava. It may come handy when one uses `AtomicReference<Disposable>` as a base class. + +The same pattern applies to `Subscription` with its `cancel()` method and with helper (internal) class `SubscriptionHelper` (but no `SequentialSubscription` or `SerialSubscription`, see next subsection). + +### Deferred requesting + +With `Flowable`s (and Reactive Streams `Publisher`s) the `request()` calls need to be deferred as well. In one form (the simpler one), the respective late `Subscription` will eventually arrive and we need to relay all previous and all subsequent request amount to its `request()` method. + +In 1.x, this behavior was implicitly provided by `rx.Subscriber` but at a high cost that had to be payed by all instances whether or not they needed this feature. + +The solution works by having the `AtomicReference` for the `Subscription` and an `AtomicLong` to store and accumulate the requests until the actual `Subscription` arrives, then atomically request all deferred value once. + +```java +static boolean deferredSetOnce(AtomicReference<Subscription> subscription, + AtomicLong requested, Subscription newSubscription) { + if (subscription.compareAndSet(null, newSubscription) { + long r = requested.getAndSet(0L); + if (r != 0) { + newSubscription.request(r); + } + return true; + } + newSubscription.cancel(); + if (subscription.get() != SubscriptionHelper.CANCELLED) { + RxJavaPlugins.onError(new IllegalStateException("Subscription already set!")); + } + return false; +} + +static void deferredRequest(AtomicReference<Subscription> subscription, + AtomicLong requested, long n) { + Subscription current = subscription.get(); + if (current != null) { + current.request(n); + } else { + BackpressureHelper.add(requested, n); + current = subscription.get(); + if (current != null) { + long r = requested.getAndSet(0L); + if (r != 0L) { + current.request(r); + } + } + } +} +``` + +In `deferredSetOnce`, if the CAS from null to the `newSubscription` succeeds, we atomically exchange the request amount to 0L and if the original value was nonzero, we request from `newSubscription`. In `deferredRequest`, if there is a `Subscription` we simply request from it directly. Otherwise, we accumulate the requests via the helper method then check again if the `Subscription` arrived or not. If it arrived in the meantime, we atomically exchange the accumulated request value and if nonzero, request it from the newly retrieved `Subscription`. This non-blocking logic makes sure that in case of concurrent invocations of the two methods, no accumulated request is left behind. + +This complex logic and methods, along with other safeguards are available in the (internal) `SubscriptionHelper` utility class and can be used like this: + +```java +final class Operator<T> implements Subscriber<T>, Subscription { + final Subscriber<? super T> child; + + final AtomicReference<Subscription> ref = new AtomicReference<>(); + final AtomicLong requested = new AtomicLong(); + + public Operator(Subscriber<? super T> child) { + this.child = child; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(ref, requested, s); + } + + @Override + public void onNext(T t) { ... } + + @Override + public void onError(Throwable t) { ... } + + @Override + public void onComplete() { ... } + + @Override + public void cancel() { + SubscriptionHelper.cancel(ref); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequested(ref, requested, n); + } +} + +Operator<T> parent = new Operator<T>(child); + +child.onSubscribe(parent); + +source.subscribe(parent); +``` + +The second form is when multiple `Subscription`s replace each other and we not only need to hold onto request amounts when there is none of them but make sure a newer `Subscription` is requested only that much the previous `Subscription`'s upstream didn't deliver. This is called **Subscription arbitration** and the relevant algorithms are quite verbose and will be omitted here. There is, however, an utility class that manages this: (internal) `SubscriptionArbiter`. + +You can extend it (to save on object headers) or have it as a field. Its main use is to send it to the downstream via `onSubscribe` and update its current `Subscription` in the current operator. Note that even though its methods are thread-safe, it is intended for swapping `Subscription`s when the current one finished emitting events. This makes sure that any newer `Subscription` is requested the right amount and not more due to production/switch race. + +```java +final SubscriptionArbiter arbiter = ... + +// ... + +child.onSubscribe(arbiter); + +// ... + +long produced; + +@Override +public void onSubscribe(Subscription s) { + arbiter.setSubscription(s); +} + +@Override +public void onNext(T value) { + produced++; + child.onNext(value); +} + +@Override +public void onComplete() { + long p = produced; + if (p != 0L) { + arbiter.produced(p); + } + subscribeNext(); +} +``` + +For better performance, most operators can count the produced element amount and issue a single `SubscriptionArbiter.produced()` call just before switching to the next `Subscription`. + + +## Atomic error management + +In some cases, multiple sources may signal a `Throwable` at the same time but the contract forbids calling `onError` multiple times. Once can, of course use the **once** approach with `AtomicReference<Throwable>` and throw out but the first one to set the `Throwable` on it. + +The alternative is to collect these `Throwable`s into a `CompositeException` as long as possible and at one point lock out the others. This works by doing a copy-on-write scheme or by linking `CompositeException`s atomically and having a terminal sentinel to indicate all further errors should be dropped. + +```java +static final Throwable TERMINATED = new Throwable(); + +static boolean addThrowable(AtomicReference<Throwable> ref, Throwable e) { + for (;;) { + Throwable current = ref.get(); + if (current == TERMINATED) { + return false; + } + Throwable next; + if (current == null) { + next = e; + } else { + next = new CompositeException(current, e); + } + if (ref.compareAndSet(current, next)) { + return true; + } + } +} + +static Throwable terminate(AtomicReference<Throwable> ref) { + return ref.getAndSet(TERMINATED); +} +``` + +as with most common logic, this is supported by the (internal) `ExceptionHelper` utility class and the (internal) `AtomicThrowable` class. + +The usage pattern looks as follows: + +```java + +final AtomicThrowable errors = ...; + +@Override +public void onError(Throwable e) { + if (errors.addThrowable(e)) { + drain(); + } else { + RxJavaPlugins.onError(e); + } +} + +void drain() { + // ... + if (errors.get() != null) { + child.onError(errors.terminate()); + return; + } + // ... +} +``` + +## Half-serialization + +Sometimes having the queue-drain, `SerializedSubscriber` or `SerializedObserver` is a bit of an overkill. Such cases include when there is only one thread calling `onNext` but other threads may call `onError` or `onComplete` concurrently. Example operators include `takeUntil` where the other source may "interrupt" the main sequence and inject an `onComplete` into it before the main source itself would complete some time later. This is what I call **half-serialization**. + +The approach uses the concepts of the deferred actions and atomic error management discussed above and has 3 methods for the `onNext`, `onError` and `onComplete` management: + +```java +public static <T> void onNext(Subscriber<? super T> subscriber, T value, + AtomicInteger wip, AtomicThrowable error) { + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + subscriber.onNext(value); + if (wip.decrementAndGet() != 0) { + Throwable ex = error.terminate(); + if (ex != null) { + subscriber.onError(ex); + } else { + subscriber.onComplete(); + } + } + } +} + +public static void onError(Subscriber<?> subscriber, Throwable ex, + AtomicInteger wip, AtomicThrowable error) { + if (error.addThrowable(ex)) { + if (wip.getAndIncrement() == 0) { + subscriber.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(ex); + } +} + +public static void onComplete(Subscriber<?> subscriber, + AtomicInteger wip, AtomicThrowable error) { + if (wip.getAndIncrement() == 0) { + Throwable ex = error.terminate(); + if (ex != null) { + subscriber.onError(ex); + } else { + subscriber.onComplete(); + } + } +} +``` + +Here, the `wip` counter indicates there is an active emission happening and if found non-zero when trying to leave the `onNext`, it is taken as indication there was a concurrent `onError` or `onComplete()` call and the child must be notified. All subsequent calls to any of these methods are ignored. In this case, the `wip` is never decremented back to zero. + +RxJava 2.x, again, supports these with the (internal) utility class `HalfSerializer` and allows targeting `Subscriber`s and `Observer`s with it. + +## Fast-path queue-drain + +In some operators, it is unlikely concurrent threads try to enter into the drain loop at the same time and having to play the full enqueue-increment-dequeue adds unnecessary overhead. + +Luckily, such situations can be detected by a simple compare-and-set attempt on the work-in-progress counter, trying to change the amount from 0 to 1. If it fails, there is a concurrent drain in progress and we revert back to the classical queue-drain logic. If succeeds, we don't enqueue anything but emit the value / perform the action right there and we try to leave the serialized section. + +```java +public void onNext(T v) { + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + child.onNext(v); + if (wip.decrementAndGet() == 0) { + break; + } + } else { + queue.offer(v); + if (wip.getAndIncrement() != 0) { + break; + } + } + drainLoop(); +} + +void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } +} + +void drainLoop() { + // the usual drain loop part after the classical getAndIncrement() +} +``` + +In this pattern, the classical `drain` is spit into `drain` and `drainLoop`. The new `drain` does the increment-check and calls `drainLoop` and `drainLoop` contains the remaining logic with the loop, emission and wip management as usual. + +On the fast path, when we try to leave it, it is possible a concurrent call to `onNext` or `drain` incremented the `wip` counter further and the decrement didn't return it to zero. This is an indication for further work and we call `drainLoop` to process it. + +## FlowableSubscriber + +Version 2.0.7 introduced a new interface, `FlowableSubscriber` that extends `Subscriber` from Reactive Streams. It has the same methods with the same parameter types but different textual rules attached to it, a set of relaxations to the Reactive Streams specification to enable better performing RxJava internals while still honoring the specification to the letter for non-RxJava consumers of `Flowable`s. + +The rule relaxations are as follows: + +- §1.3 relaxation: `onSubscribe` may run concurrently with onNext in case the `FlowableSubscriber` calls `request()` from inside `onSubscribe` and it is the resposibility of `FlowableSubscriber` to ensure thread-safety between the remaining instructions in `onSubscribe` and `onNext`. +- §2.3 relaxation: calling `Subscription.cancel` and `Subscription.request` from `FlowableSubscriber.onComplete()` or `FlowableSubscriber.onError()` is considered a no-operation. +- §2.12 relaxation: if the same `FlowableSubscriber` instance is subscribed to multiple sources, it must ensure its `onXXX` methods remain thread safe. +- §3.9 relaxation: issuing a non-positive `request()` will not stop the current stream but signal an error via `RxJavaPlugins.onError`. + +When a `Flowable` gets subscribed by a `Subscriber`, an `instanceof` check will detect `FlowableSubscriber` and not apply the `StrictSubscriber` wrapper that makes sure the relaxations don't happen. In practice, ensuring rule §3.9 has the most overhead because a bad request may happen concurrently with an emission of any normal event and thus has to be serialized with one of the methods described in previous sections. + +**In fact, 2.x was always implemented in this relaxed manner thus looking at existing code and style is the way to go.** + +Therefore, it is strongly recommended one implements custom intermediate and end operators via `FlowableSubscriber`. + +From a source operator's perspective, extending the `Flowable` class and implementing `subscribeActual` has no need for +dispatching over the type of the `Subscriber`; the backing infrastructure already applies wrapping if necessary thus one can be sure in `subscribeActual(Subscriber<? super T> s)` the parameter `s` is a `FlowableSubscriber`. (The signature couldn't be changed for compatibility reasons.) Since the two interfaces on the Java level are the same, no real preferential treating is necessary within sources (i.e., don't cast `s` into `FlowableSubscriber`. + +The other base reactive consumers, `Observer`, `SingleObserver`, `MaybeObserver` and `CompletableObserver` don't need such relaxation, because + +- they are only defined and used in RxJava (i.e., no other library implemented with them), +- they were conceptionally always derived from the relaxed `Subscriber` RxJava had, +- they don't have backpressure thus no `request()` call that would introduce another concurrency to think about, +- there is no way to trigger emission from upstream before `onSubscribe(Disposable)` returns in standard operators (again, no `request()` method). + +# Backpressure and cancellation + +Backpressure (or flow control) in Reactive Streams is the means to tell the upstream how many elements to produce or to tell it to stop producing elements altogether. Unlike the name suggest, there is no physical pressure preventing the upstream from calling `onNext` but the protocol to honor the request amount. + +## Replenishing + +When dealing with basic transformations in a flow, there are often cases when the number of items the upstream sends should be different what the downstream receives. Some operators may want to filter out certain items, others would batch up items before sending one item to the downstream. + +However, when an item is not forwarded by an operator, the downstream has no way of knowing its `request(1)` triggered an item generation that got dropped/buffered. Therefore, it can't know to (nor should it) repeat `request(1)` to "nudge" the source somewhere more upstream to try producing another item which now hopefully will result in an item being received by the downstream. Unlike, say the ACK-NACK based protocols, the requesting specified by the Reactive Streams are to be treated as cumulative. In the previous example, an impatient downstream would have 2 outstanding requests. + +Therefore, if an operator is not guaranteed to relay an upstream item to downstream, and thus keeping a 1:1 ratio, it has the duty to keep requesting items from the upstream until said operator ends up in a position to supply an item to the downstream. + +This may sound a bit complicated, but perhaps a demonstration of a `filter` operator can help: + +```java +final class FilterOddSubscriber implements FlowableSubscriber<Integer>, Subscription { + + final Subscriber<? super Integer> downstream; + + Subscription upstream; + + // ... + + @Override + public void onSubscribe(Subscription s) { + if (upstream != null) { + s.cancel(); + } else { + upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(Integer item) { + if (item % 2 != 0) { + downstream.onNext(item); + } else { + upstream.request(1); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + // the rest omitted for brevity +} +``` + +In such operators, thee downstream's `request` calls are forwarded to the upstream as is, and for `n` times (at most, unless completed) the `onNext` will be invoked. In this operator, we look for the odd numbers of the flow. If we find one, the downstream will be notified. If the incoming item is even, we won't forward it to the downstream. However, the downstream is still expecting at least 1 item, but since the upstream and downstream practically talk to each other directly, the upstream considers its duty to generate items fulfilled. This misalignment is then resolved by requesting 1 more item from the upstream for the previous ignored item. If more items arrive that get ignored, more will be requested as replenishment. + +Given that backpressure involves some overhead in the form of one or more atomic operations, requesting one by one could add a lot of overhead if the number of items filtered out is significantly more than those that passed through. If necessary, this situation can be either solved by [decoupling the upstream and downstream's request management](#stable-prefetching) or using an RxJava-specific type and protocol extension in the form of [ConditionalSubscriber](#conditionalsubscriber)s. + +## Stable prefetching + +In a previous section, we saw primitives to deal with request accounting and delayed `Subscriptions`, but often, operators have to react to request amount changes as well. This comes up when the operator has to decouple the downstream request amount from the amount it requests from upstream, such as `observeOn`. + +Such logic can get quite complicated in operators but one of the simplest manifestation can be the `rebatchRequest` operator that combines request management with serialization to ensure that upstream is requested with a predictable pattern no matter how the downstream requested (less, more or even unbounded): + +```java +final class RebatchRequests<T> extends AtomicInteger +implements FlowableSubscriber<T>, Subscription { + + final Subscriber<? super T> child; + + final AtomicLong requested; + + final SpscArrayQueue<T> queue; + + final int batchSize; + + final int limit; + + Subscription s; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + long emitted; + + public RebatchRequests(Subscriber<? super T> child, int batchSize) { + this.child = child; + this.batchSize = batchSize; + this.limit = batchSize - (batchSize >> 2); // 75% of batchSize + this.requested = new AtomicLong(); + this.queue = new SpscArrayQueue<T>(batchSize); + } + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + child.onSubscribe(this); + s.request(batchSize); + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + } + + void drain() { + // see next code example + } +} +``` + +Here we extend `AtomicInteger` since the work-in-progress counting happens more often and is worth avoiding the extra indirection. The class extends `Subscription` and it hands itself to the `child` `Subscriber` to capture its `request()` (and `cancel()`) calls and route it to the main `drain` logic. Some operators need only this, some other operators (such as `observeOn` not only routes the downstream request but also does extra cancellations (cancels the asynchrony providing `Worker` as well) in its `cancel()` method. + +**Important**: when implementing operators for `Flowable` and `Observable` in RxJava 2.x, you are not allowed to pass along an upstream `Subscription` or `Disposable` to the child `Subscriber`/`Observer` when the operator logic itself doesn't require hooking the `request`/`cancel`/`dispose` calls. The reason for this is how operator-fusion is implemented on top of `Subscription` and `Disposable` passing through `onSubscribe` in RxJava 2.x (and in **Reactor 3**). See the next section about operator-fusion. There is no fusion in `Single`, `Completable` or `Maybe` (because there is no requesting or unbounded buffering with them) and their operators can pass the upstream `Disposable` along as is. + +Next comes the `drain` method whose pattern appears in many operators (with slight variations on how and what the emission does). + +```java +void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + long r = requested.get(); + long e = 0L; + long f = emitted; + + while (e != r) { + if (cancelled) { + return; + } + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + } + + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + child.onComplete(); + return; + } + + if (empty) { + break; + } + + child.onNext(v); + + e++; + if (++f == limit) { + s.request(f); + f = 0L; + } + } + + if (e == r) { + if (cancelled) { + return; + } + + if (done) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + if (queue.isEmpty()) { + child.onComplete(); + return; + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + + emitted = f; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } +} +``` + +This particular pattern is called the **stable-request queue-drain**. Another variation doesn't care about request amount stability towards upstream and simply requests the amount it delivered to the child: + +```java +void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + return; + } + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + } + + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + child.onComplete(); + return; + } + + if (empty) { + break; + } + + child.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + return; + } + + if (done) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + if (queue.isEmpty()) { + child.onComplete(); + return; + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + s.request(e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } +} +``` + +The third variation allows delaying a potential error until the upstream has terminated and all normal elements have been delivered to the child: + +```java +final boolean delayError; + +void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + return; + } + boolean d = done; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + } + + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onComplete(); + } + return; + } + + if (empty) { + break; + } + + child.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + return; + } + + if (done) { + if (delayError) { + if (queue.isEmpty()) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onComplete(); + } + return; + } + } else { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + return; + } + if (queue.isEmpty()) { + child.onComplete(); + return; + } + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + s.request(e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } +} +``` + +If the downstream cancels the operator, the `queue` may still hold elements which may get referenced longer than expected if the operator chain itself is referenced in some way. On the user level, applying `onTerminateDetach` will forget all references going upstream and downstream and can help with this situation. On the operator level, RxJava 2.x usually calls `clear()` on the `queue` when the sequence is cancelled or ends before the queue is drained naturally. This requires some slight change to the drain loop: + +```java +final boolean delayError; + +@Override +public void cancel() { + cancelled = true; + s.cancel(); + if (getAndIncrement() == 0) { + queue.clear(); // <---------------------------- + } +} + +void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + queue.clear(); // <---------------------------- + return; + } + boolean d = done; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + queue.clear(); // <---------------------------- + child.onError(ex); + return; + } + } + + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onComplete(); + } + return; + } + + if (empty) { + break; + } + + child.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + queue.clear(); // <---------------------------- + return; + } + + if (done) { + if (delayError) { + if (queue.isEmpty()) { + Throwable ex = error; + if (ex != null) { + child.onError(ex); + } else { + child.onComplete(); + } + return; + } + } else { + Throwable ex = error; + if (ex != null) { + queue.clear(); // <---------------------------- + child.onError(ex); + return; + } + if (queue.isEmpty()) { + child.onComplete(); + return; + } + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + s.request(e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } +} +``` + +Since the queue is single-producer-single-consumer, its `clear()` must be called from a single thread - which is provided by the serialization loop and is enabled by the `getAndIncrement() == 0` "half-loop" inside `cancel()`. + +An important note on the order of calls to `done` and the `queue`'s state: + +```java +boolean d = done; +T v = queue.poll(); +``` + +and + +```java +boolean d = done; +boolean empty = queue.isEmpty(); +``` + +These must happen in the order specified. If they were swapped, it is possible when the drain runs asynchronously to an `onNext`/`onComplete()`, the queue may appear empty at first, then it gets elements followed by `done = true` and a late `done` check in the drain loop may complete the sequence thinking it delivered all values there was. + +## Single valued results + +Sometimes an operator only emits one single value at some point instead of emitting more or all of its sources. Such operators include `fromCallable`, `reduce`, `any`, `all`, `first`, etc. + +The classical queue-drain works here but is a bit of an overkill to allocate objects to store the work-in-progress counter, request accounting and the queue itself. These elements can be reduced to a single state-machine with one state counter object - often inlinded by extending AtomicInteger - and a plain field for storing the single value to be emitted. + +The state machine handing the possible concurrent downstream requests and normal completion path is a bit complicated to show here and is quite easy to get wrong. + +RxJava 2.x supports this kind of behavior through the (internal) `DeferredScalarSubscription` for operators without an upstream source (`fromCallable`) and the (internal) `DeferredScalarSubscriber` for reduce-like operators with an upstream source. + +Using the `DeferredScalarSubscription` is straightforward, one creates it, sends it to the downstream via `onSubscribe` and later on calls `complete(T)` to signal the end with a single value: + +```java +DeferredScalarSubscription<Integer> dss = new DeferredScalarSubscription<>(child); +child.onSubscribe(dss); + +dss.complete(1); +``` + +Using the `DeferredScalarSubscriber` requires more coding and extending the class itself: + +```java +final class Counter extends DeferredScalarSubscriber<Object, Integer> { + public Counter(Subscriber<? super Integer> child) { + super(child); + value = 0; + hasValue = true; + } + + @Override + public void onNext(Object t) { + value++; + } +} +``` + +By default, the `DeferredScalarSubscriber.onSubscribe()` requests `Long.MAX_VALUE` from the upstream (but the method can be overridden in subclasses). + +## Single-element post-complete + +Some operators have to modulate a sequence of elements in a 1:1 fashion but when the upstream terminates, they need to produce a final element followed by a terminal event (usually `onComplete`). + +```java +final class OnCompleteEndWith implements Subscriber<T>, Subscription { + final Subscriber<? super T> child; + + final T finalElement; + + Subscription s; + + public OnCompleteEndWith(Subscriber<? super T> child, T finalElement) { + this.child = child; + this.finalElement = finalElement; + } + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + child.onSubscribe(this); + } + + @Override + public void onNext(T t) { + child.onNext(t); + } + + @Overide + public void onError(Throwable t) { + child.onError(t); + } + + @Override + public void onComplete() { + child.onNext(finalElement); + child.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } +} +``` + +This works if the downstream request more than the upstream produces + 1, otherwise the call to `onComplete` may overflow the child `Subscriber`. + +Heavyweight solutions such as queue-drain or `SubscriptionArbiter` with `ScalarSubscriber` can be used here, however, there is a more elegant solution to the problem. + +The idea is that request amounts occupy only 63 bits of a 64 bit (atomic) long type. If we'd mask out the lower 63 bits when working with the amount, we can use the most significant bit to indicate the upstream sequence has finished and then on, any 0 to n request amount change can trigger the emission of the `finalElement`. Since a downstream `request()` can race with an upstream `onComplete`, marking the bit atomically via a compare-and-set ensures correct state transition. + +For this, the `OnCompleteEndWith` has to be changed by adding an `AtomicLong` for accounting requests, a long for counting the production, then updating `request()` and `onComplete()` methods: + +```java + +final class OnCompleteEndWith +extends AtomicLong +implements FlowableSubscriber<T>, Subscription { + final Subscriber<? super T> child; + + final T finalElement; + + Subscription s; + + long produced; + + static final class long REQUEST_MASK = Long.MAX_VALUE; // 0b01111...111L + static final class long COMPLETE_MASK = Long.MIN_VALUE; // 0b10000...000L + + public OnCompleteEndWith(Subscriber<? super T> child, T finalElement) { + this.child = child; + this.finalElement = finalElement; + } + + @Override + public void onSubscribe(Subscription s) { ... } + + @Override + public void onNext(T t) { + produced++; // <------------------------ + child.onNext(t); + } + + @Overide + public void onError(Throwable t) { ... } + + @Override + public void onComplete() { + long p = produced; + if (p != 0L) { + produced = 0L; + BackpressureHelper.produced(this, p); + } + + for (;;) { + long current = get(); + if ((current & COMPLETE_MASK) != 0) { + break; + } + if ((current & REQUEST_MASK) != 0) { + lazySet(Long.MIN_VALUE + 1); + child.onNext(finalElement); + child.onComplete(); + return; + } + if (compareAndSet(current, COMPLETE_MASK)) { + break; + } + } + } + + @Override + public void request(long n) { + for (;;) { + long current = get(); + if ((current & COMPLETE_MASK) != 0) { + if (compareAndSet(current, COMPLETE_MASK + 1)) { + child.onNext(finalElement); + child.onComplete(); + } + break; + } + long u = BackpressureHelper.addCap(current, n); + if (compareAndSet(current, u)) { + s.request(n); + break; + } + } + } + + @Override + public void cancel() { ... } +} +``` + +RxJava 2 has a couple of operators, `materialize`, `mapNotification`, `onErrorReturn`, that require this type of behavior and for that, the (internal) `SinglePostCompleteSubscriber` class captures the algorithms above: + +```java +final class OnCompleteEndWith<T> extends SinglePostCompleteSubscriber<T, T> { + final Subscriber<? super T> child; + + public OnCompleteEndWith(Subscriber<? super T> child, T finalElement) { + this.child = child; + this.value = finalElement; + } + + @Override + public void onNext(T t) { + produced++; // <------------------------ + child.onNext(t); + } + + @Overide + public void onError(Throwable t) { ... } + + @Override + public void onComplete() { + complete(value); + } +} +``` + +## Multi-element post-complete + +Certain operators may need to emit multiple elements after the main sequence completes, which may or may not relay elements from the live upstream before its termination. An example operator is `buffer(int, int)` when the skip < size yielding overlapping buffers. In this operator, it is possible when the upstream completes, several overlapping buffers are waiting to be emitted to the child but that has to happen only when the child actually requested more buffers. + +The state machine for this case is complicated but RxJava has two (internal) utility methods on `QueueDrainHelper` for dealing with the situation: + +```java +<T> void postComplete(Subscriber<? super T> actual, + Queue<T> queue, + AtomicLong state, + BooleanSupplier isCancelled); + +<T> boolean postCompleteRequest(long n, + Subscriber<? super T> actual, + Queue<T> queue, + AtomicLong state, + BooleanSupplier isCancelled); +``` + +They take the child `Subscriber`, the queue to drain from, the state holding the current request amount and a callback to see if the downstream cancelled the sequence. + +Usage of these methods is as follows: + +```java +final class EmitTwice<T> extends AtomicLong +implements FlowableSubscriber<T>, Subscription, BooleanSupplier { + final Subscriber<? super T> child; + + final ArrayDeque<T> buffer; + + volatile boolean cancelled; + + Subscription s; + + long produced; + + public EmitTwice(Subscriber<? super T> child) { + this.child = child; + this.buffer = new ArrayDeque<>(); + } + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + child.onSubscribe(this); + } + + @Override + public void onNext(T t) { + produced++; + buffer.offer(t); + child.onNext(t); + } + + @Override + public void onError(Throwable t) { + buffer.clear(); + child.onError(t); + } + + @Override + public void onComplete() { + long p = produced; + if (p != 0L) { + produced = 0L; + BackpressureHelper.produced(this, p); + } + QueueDrainHelper.postComplete(child, buffer, this, this); + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + } + + @Override + public void request(long n) { + if (!QueueDrainHelper.postCompleteRequest(n, child, buffer, this, this)) { + s.request(n); + } + } +} +``` + +# Creating operator classes + +Creating operator implementations in 2.x is simpler than in 1.x and incurs less allocation as well. You have the choice to implement your operator as a `Subscriber`-transformer to be used via `lift` or as a fully-fledged base reactive class. + +## Operator by extending a base reactive class + +In 1.x, extending `Observable` was possible but convoluted because you had to implement the `OnSubscribe` interface separately and pass it to `Observable.create()` or to the `Observable(OnSubscribe)` protected constructor. + +In 2.x, all base reactive classes are abstract and you can extend them directly without any additional indirection: + +```java +public final class FlowableMyOperator extends Flowable<Integer> { + final Publisher<Integer> source; + + public FlowableMyOperator(Publisher<Integer> source) { + this.source = source; + } + + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + source.map(v -> v + 1).subscribe(s); + } +} +``` + +When taking other reactive types as inputs in these operators, it is recommended one defines the base reactive interfaces instead of the abstract classes, allowing better interoperability between libraries (especially with `Flowable` operators and other Reactive Streams `Publisher`s). To recap, these are the class-interface pairs: + + - `Flowable` - `Publisher` - `FlowableSubscriber`/`Subscriber` + - `Observable` - `ObservableSource` - `Observer` + - `Single` - `SingleSource` - `SingleObserver` + - `Completable` - `CompletableSource` - `CompletableObserver` + - `Maybe` - `MaybeSource` - `MaybeObserver` + +RxJava 2.x locks down `Flowable.subscribe` (and the same methods in the other types) in order to provide runtime hooks into the various flows, therefore, implementors are given the `subscribeActual()` to be overridden. When it is invoked, all relevant hooks and wrappers have been applied. Implementors should avoid throwing unchecked exceptions as the library generally can't deliver it to the respective `Subscriber` due to lifecycle restrictions of the Reactive Streams specification and sends it to the global error consumer via `RxJavaPlugins.onError`. + +Unlike in 1.x, In the example above, the incoming `Subscriber` is simply used directly for subscribing again (but still at most once) without any kind of wrapping. In 1.x, one needs to call `Subscribers.wrap` to avoid double calls to `onStart` and cause unexpected double initialization or double-requesting. + +Unless one contributes a new operator to RxJava, working with such classes may become tedious, especially if they are intermediate operators: + +```java +new FlowableThenSome( + new FlowableOther( + new FlowableMyOperator(Flowable.range(1, 10).map(v -> v * v)) + ) +) +``` + +This is an unfortunate effect of Java lacking extension method support. A possible ease on this burden is by using `compose` to have fluent inline application of the custom operator: + +```java +Flowable.range(1, 10).map(v -> v * v) +.compose(f -> new FlowableOperatorWithParameter(f, 10)); + +Flowable.range(1, 10).map(v -> v * v) +.compose(FlowableMyOperator::new); +``` + +## Operator targeting lift() + +The alternative to the fluent application problem is to have a `Subscription`-transformer implemented instead of extending the whole reactive base class and use the respective type's `lift()` operator to get it into the sequence. + +First one has to implement the respective `XOperator` interface: + +```java +public final class MyOperator implements FlowableOperator<Integer, Integer> { + + @Override + public Subscriber<? super Integer> apply(Subscriber<? super Integer> child) { + return new Op(child); + } + + static final class Op implements FlowableSubscriber<Integer>, Subscription { + final Subscriber<? super Integer> child; + + Subscription s; + + public Op(Subscriber<? super Integer> child) { + this.child = child; + } + + @Override + pubic void onSubscribe(Subscription s) { + this.s = s; + child.onSubscribe(this); + } + + @Override + public void onNext(Integer v) { + child.onNext(v * v); + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onComplete() { + child.onComplete(); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void request(long n) { + s.request(n); + } + } +} +``` + +You may recognize that implementing operators via extension or lifting looks quite similar. In both cases, one usually implements a `FlowableSubscriber` (`Observer`, etc) that takes a downstream `Subscriber`, implements the business logic in the `onXXX` methods and somehow (manually or as part of `lift()`'s lifecycle) gets subscribed to an upstream source. + +The benefit of applying the Reactive Streams design to all base reactive types is that each consumer type is now an interface and can be applied to operators that have to extend some class. This was a pain in 1.x because `Subscriber` and `SingleSubscriber` are classes themselves, plus `Subscriber.request()` is a protected-final method and an operator's `Subscriber` can't implement the `Producer` interface at the same time. In 2.x there is no such problem and one can have both `Subscriber`, `Subscription` or even `Observer` together in the same consumer type. + +# Operator fusion + +Operator fusion has the premise that certain operators can be combined into one single operator (macro-fusion) or their internal data structures shared between each other (micro-fusion) that allows fewer allocations, lower overhead and better performance. + +This advanced concept was invented, worked out and studied in the [Reactive-Streams-Commons](https://github.com/reactor/reactive-streams-commons) research project manned by the leads of RxJava and Project Reactor. Both libraries use the results in their implementation, which look the same but are incompatible due to different classes and packages involved. In addition, RxJava 2.x's approach is a more polished version of the invention due to delays between the two project's development. + +Since operator-fusion is optional, you may chose to not bother making your operator fusion-enabled. The `DeferredScalarSubscription` is fusion-enabled and needs no additional development in this regard though. + +If you chose to ignore operator-fusion, you still have to follow the requirement of never forwarding a `Subscription`/`Disposable` coming through `onSubscribe` of `Subscriber`/`Observer` as this may break the fusion protocol and may skip your operator's business logic entirely: + +```java +final class SomeOp<T> implements Subscriber<T>, Subscription { + + // ... + Subscription s; + + public void onSubscribe(Subscription s) { + this.s = s; + child.onSubscribe(this); // <--------------------------- + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void request(long n) { + s.request(n); + } + + // ... +} +``` + +Yes, this adds one more indirection between operators but it is still cheap (and would be necessary for the operator anyway) but enables huge performance gains with the right chain of operators. + +## Generations + +Given this novel approach, a generation number can be assigned to various implementation styles of reactive architectures: + +#### Generation 0 +These are the classical libraries that either use `java.util.Observable` or are listener based (Java Swing's `ActionListener`). Their common property is that they don't support composition (of events and cancellation). See also **Google Agera**. + +#### Generation 1 +This is the level of the **Rx.NET** library (even up to 3.x) that supports composition, but has no notion for backpressure and doesn't properly support synchronous cancellation. Many JavaScript libraries such as **RxJS 5** are still on this level. See also **Google gRPC**. + +#### Generation 2 +This is what **RxJava 1.x** is categorized, it supports composition, backpressure and synchronous cancellation along with the ability to lift an operator into a sequence. + +#### Generation 3 +This is the level of the Reactive Streams based libraries such as **Reactor 2** and **Akka-Stream**. They are based upon a specification that evolved out of RxJava but left behind its drawbacks (such as the need to return anything from `subscribe()`). This is incompatible with RxJava 1.x and thus 2.x had to be rewritten from scratch. + +#### Generation 4 +This level expands upon the Reactive Streams interfaces with operator-fusion (in a compatible fashion, that is, op-fusion is optional between two stages and works without them). **Reactor 3** and **RxJava 2** are at this level. The material around **Akka-Stream** mentions operator-fusion as well, however, **Akka-Stream** is not a native Reactive Streams implementation (requires a materializer to get a `Publisher` out) and as such it is only Gen 3. + +There are discussions among the 4th generation library providers to have the elements of operator-fusion standardized in Reactive Streams 2.0 specification (or in a neighboring extension) and have **RxJava 3** and **Reactor 4** work together on that aspect as well. + +## Components + +### Callable and ScalarCallable + +Certain `Flowable` sources, similar to `Single` or `Completable` are known to ever emit zero or one item and that single item is known to be constant or is computed synchronously. Well known examples of this are `just()`, `empty()` and `fromCallable`. Subscribing to these sources, like any other sources, adds the same infrastructure overhead which can often be avoided if the consumer could just pick or have the item calculated on the spot. + +For example, `just` and `empty` appears as the mapping result of a `flatMap` operation: + +```java +source.flatMap(v -> { + if (v % 2 == 0) { + return just(v); + } + return empty(); +}) +``` + +Here, if we'd somehow recognize that `empty()` won't emit a value but only `onComplete` we could simply avoid subscribing to it inside `flatMap`, saving on the overhead. Similarly, recognizing that `just` emits exactly one item we can route it differently inside `flatMap` and again, avoiding creating a lot of objects to get to the same single item. + +In other times, knowing the emission property can simplify or chose a different operator instead of the applied one. For example, applying `flatMap` to an `empty()` source has no use since there won't be any item to be flattened into a sequence; the whole flattened sequence is going to be empty. Knowing that a source is `just` to `flatMap`, there is no need for the complicated inner mechanisms as there is going to be only one mapped inner source and one can subscribe the downstream's `Subscriber` to it directly. + +```java +Flowable.just(1).flatMap(v -> Flowable.range(v, 5)).subscribe(...); + +// in some specialized operator: + +T value; // from just() + +@Override +public void subscribeActual(Subscriber<? super T> s) { + mapper.apply(value).subscribe(s); +} +``` + +There could be other sources with these properties, therefore, RxJava 2 uses the `io.reactivex.internal.fusion.ScalarCallable` and `java.util.Callable` interfaces to indicate a source is a constant or sequentially computable. When a source `Flowable` or `Observable` is marked with one of these interfaces, many fusion enabled operators will perform special actions to avoid the overhead of a normal and general source. + +We use Java's own and preexisting `java.util.Callable` interface to indicate a synchronously computable source. The `ScalarCallable` is an extension to this interface by which it suppresses the `throws Exception` of `Callable.call()`: + +```java +interface Callable<T> { + T call() throws Exception; +} + +interface ScalarCallable<T> extends Callable<T> { + @Override + T call(); +} +``` + +The reason for the two separate interfaces is that if a source is constant, like `just`, one can perform assembly-time optimizations with it knowing that each regular `subscribe` invocation would have resulted in the same single value. + +`Callable` denotes sources, such as `fromCallable` that indicates the single value has to be calculated at runtime of the flow. By this logic, you can see that `ScalarCallable` is a `Callable` on its own right because the constant can be "calculated" as late as the runtime phase of the flow. + +Since Reactive Streams forbids using `null`s as emission values, we can use `null` in `(Scalar)Callable` marked sources to indicate there is no value to be emitted, thus one can't mistake an user's `null` with the empty indicator `null`. For example, this is how `empty()` is implemented: + +```java +final class FlowableEmpty extends Flowable<Object> implements ScalarCallable<Object> { + @Override + public void subscribeActual(Subscriber<? super T> s) { + EmptySubscription.complete(s); + } + + @Override + public Object call() { + return null; // interpreted as no value available + } +} +``` + +Sources implementing `Callable` may throw checked exceptions from `call()` which is handled by the consumer operators as an indication to signal `onError` in an operator specific manner (such as delayed). + +```java +final class FlowableIOException extends Flowable<Object> implements Callable<Object> { + @Override + public void subscribeActual(Subscriber<? super T> s) { + EmptySubscription.error(new IOException(), s); + } + + @Override + public Object call() throws Exception { + throw new IOException(); + } +} +``` + +However, implementors of `ScalarCallable` should avoid throwing any exception and limit the code in `call()` be constant or simple computation that can be legally executed during assembly time. + +As the consumer of sources, one may want to deal with such kind of special `Flowable`s or `Observable`s. For example, if you create an operator that can leverage the knowledge of a single element source as its main input, you can check the types and extract the value of a `ScalarCallable` at assembly time right in the operator: + +```java +// Flowable.java +public final Flowable<Integer> plusOne() { + if (this instanceof ScalarCallable) { + Integer value = ((ScalarCallable<Integer>)this).call(); + if (value == null) { + return empty(); + } + return just(value + 1); + } + return cast(Integer.class).map(v -> v + 1); +} +``` + +or as a `FlowableTransformer`: + +```java +FlowableTransformer<Integer, Integer> plusOneTransformer = source -> { + if (source instanceof ScalarCallable) { + Integer value = ((ScalarCallable<Integer>)source).call(); + if (value == null) { + return empty(); + } + return just(value + 1); + } + return source.map(v -> v + 1); +}; +``` + +However, it is not mandatory to handle `ScalarCallable`s and `Callable`s separately. Since the former extends the latter, the type check can be deferred till subscription time and handled with the same code path: + +```java +final class FlowablePlusOne extends Flowable<Integer> { + final Publisher<Integer> source; + + FlowablePlusOne(Publisher<Integer> source) { + this.source = source; + } + + @Override + public void subscribeActual(Subscriber<? super Integer> s) { + if (source instanceof Callable) { + Integer value; + + try { + value = ((Callable<Integer>)source).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + s.onSubscribe(new ScalarSubscription<Integer>(s, value + 1)); + } else { + new FlowableMap<>(source, v -> v + 1).subscribe(s); + } + } +} +``` + +### ConditionalSubscriber + +TBD + +### QueueSubscription and QueueDisposable + +TBD + +# Example implementations + +TBD + +## `map` + `filter` hybrid + +TBD + +## Ordered `merge` + +TBD diff --git a/docs/_Footer.md b/docs/_Footer.md new file mode 100644 index 0000000000..8ecc48ce6f --- /dev/null +++ b/docs/_Footer.md @@ -0,0 +1,2 @@ +**Copyright (c) 2016-present, RxJava Contributors.** +[Twitter @RxJava](https://twitter.com/#!/RxJava) | [Gitter @RxJava](https://gitter.im/ReactiveX/RxJava) diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md new file mode 100644 index 0000000000..1fe0339407 --- /dev/null +++ b/docs/_Sidebar.md @@ -0,0 +1,31 @@ +* [Introduction](https://github.com/ReactiveX/RxJava/wiki/Home) +* [Getting Started](https://github.com/ReactiveX/RxJava/wiki/Getting-Started) +* [How to Use RxJava](https://github.com/ReactiveX/RxJava/wiki/How-To-Use-RxJava) +* [Reactive Streams](https://github.com/ReactiveX/RxJava/wiki/Reactive-Streams) +* [The reactive types of RxJava](https://github.com/ReactiveX/RxJava/wiki/Observable) +* [Schedulers](https://github.com/ReactiveX/RxJava/wiki/Scheduler) +* [Subjects](https://github.com/ReactiveX/RxJava/wiki/Subject) +* [Error Handling](https://github.com/ReactiveX/RxJava/wiki/Error-Handling) +* [Operators (Alphabetical List)](https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-Observable-Operators) + * [Async](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) + * [Blocking](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) + * [Combining](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables) + * [Conditional & Boolean](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) + * [Connectable](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) + * [Creation](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables) + * [Error management](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators) + * [Filtering](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) + * [Mathematical and Aggregate](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) + * [Parallel flows](https://github.com/ReactiveX/RxJava/wiki/Parallel-flows) + * [String](https://github.com/ReactiveX/RxJava/wiki/String-Observables) + * [Transformation](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables) + * [Utility](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) + * [Notable 3rd party Operators (Alphabetical List)](https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-3rd-party-Operators) +* [Plugins](https://github.com/ReactiveX/RxJava/wiki/Plugins) +* [How to Contribute](https://github.com/ReactiveX/RxJava/wiki/How-to-Contribute) +* [Writing operators](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0) +* [Backpressure](https://github.com/ReactiveX/RxJava/wiki/Backpressure-(2.0)) + * [another explanation](https://github.com/ReactiveX/RxJava/wiki/Backpressure) +* [JavaDoc](http://reactivex.io/RxJava/2.x/javadoc) +* [Coming from RxJava 1](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0) +* [Additional Reading](https://github.com/ReactiveX/RxJava/wiki/Additional-Reading) diff --git a/docs/_Sidebar.md.md b/docs/_Sidebar.md.md new file mode 100644 index 0000000000..1fe0339407 --- /dev/null +++ b/docs/_Sidebar.md.md @@ -0,0 +1,31 @@ +* [Introduction](https://github.com/ReactiveX/RxJava/wiki/Home) +* [Getting Started](https://github.com/ReactiveX/RxJava/wiki/Getting-Started) +* [How to Use RxJava](https://github.com/ReactiveX/RxJava/wiki/How-To-Use-RxJava) +* [Reactive Streams](https://github.com/ReactiveX/RxJava/wiki/Reactive-Streams) +* [The reactive types of RxJava](https://github.com/ReactiveX/RxJava/wiki/Observable) +* [Schedulers](https://github.com/ReactiveX/RxJava/wiki/Scheduler) +* [Subjects](https://github.com/ReactiveX/RxJava/wiki/Subject) +* [Error Handling](https://github.com/ReactiveX/RxJava/wiki/Error-Handling) +* [Operators (Alphabetical List)](https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-Observable-Operators) + * [Async](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) + * [Blocking](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) + * [Combining](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables) + * [Conditional & Boolean](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) + * [Connectable](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) + * [Creation](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables) + * [Error management](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators) + * [Filtering](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) + * [Mathematical and Aggregate](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) + * [Parallel flows](https://github.com/ReactiveX/RxJava/wiki/Parallel-flows) + * [String](https://github.com/ReactiveX/RxJava/wiki/String-Observables) + * [Transformation](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables) + * [Utility](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) + * [Notable 3rd party Operators (Alphabetical List)](https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-3rd-party-Operators) +* [Plugins](https://github.com/ReactiveX/RxJava/wiki/Plugins) +* [How to Contribute](https://github.com/ReactiveX/RxJava/wiki/How-to-Contribute) +* [Writing operators](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0) +* [Backpressure](https://github.com/ReactiveX/RxJava/wiki/Backpressure-(2.0)) + * [another explanation](https://github.com/ReactiveX/RxJava/wiki/Backpressure) +* [JavaDoc](http://reactivex.io/RxJava/2.x/javadoc) +* [Coming from RxJava 1](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0) +* [Additional Reading](https://github.com/ReactiveX/RxJava/wiki/Additional-Reading) diff --git a/gradle.properties b/gradle.properties index 5d8d80efe5..53ddd3a0ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ release.scope=patch -release.version=2.0.0-DP0-SNAPSHOT +release.version=2.2.0-SNAPSHOT diff --git a/gradle/buildViaTravis.sh b/gradle/buildViaTravis.sh index 4456600e36..f8ef3a8440 100755 --- a/gradle/buildViaTravis.sh +++ b/gradle/buildViaTravis.sh @@ -12,14 +12,13 @@ export GRADLE_OPTS=-Xmx1024m if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew -Prelease.useLastTag=true build + ./gradlew -PreleaseMode=pr build elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' - ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace + ./gradlew -PreleaseMode=branch -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build --stacktrace elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace + ./gradlew -PreleaseMode=full -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build --stacktrace else echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' - ./gradlew -Prelease.useLastTag=true build fi diff --git a/gradle/javadoc_cleanup.gradle b/gradle/javadoc_cleanup.gradle new file mode 100644 index 0000000000..518d7a1d51 --- /dev/null +++ b/gradle/javadoc_cleanup.gradle @@ -0,0 +1,36 @@ +// remove the excessive whitespaces between method arguments in the javadocs +task javadocCleanup(dependsOn: "javadoc") doLast { + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/Flowable.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/Observable.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/Single.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/Maybe.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/Completable.html')); + + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/flowables/ConnectableFlowable.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/observables/ConnectableObservable.html')); + + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/subjects/ReplaySubject.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/processors/ReplayProcessor.html')); + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/plugins/RxJavaPlugins.html')); + + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/parallel/ParallelFlowable.html')); +} + +def fixJavadocFile(file) { + println("Cleaning up: " + file); + String fileContents = file.getText('UTF-8') + + // lots of spaces after the previous method argument + fileContents = fileContents.replaceAll(",\\s{4,}", ",\n "); + + // lots of spaces after the @NonNull annotations + fileContents = fileContents.replaceAll("@NonNull</a>\\s{4,}", "@NonNull</a> "); + + // lots of spaces after the @Nullable annotations + fileContents = fileContents.replaceAll("@Nullable</a>\\s{4,}", "@Nullable</a> "); + + file.setText(fileContents, 'UTF-8'); +} + +javadocJar.dependsOn javadocCleanup +build.dependsOn javadocCleanup \ No newline at end of file diff --git a/gradle/push_javadoc.sh b/gradle/push_javadoc.sh new file mode 100644 index 0000000000..8bd3ef0f87 --- /dev/null +++ b/gradle/push_javadoc.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# ---------------------------------------------------------- +# Automatically push back the generated JavaDocs to gh-pages +# ---------------------------------------------------------- +# based on https://gist.github.com/willprice/e07efd73fb7f13f917ea + +# specify the common address for the repository +targetRepo=github.com/ReactiveX/RxJava.git +# ======================================================================= + +# only for main pushes, for now +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo -e "Pull request detected, skipping JavaDocs pushback." + exit 0 +fi + +# get the current build tag if any +buildTag="$TRAVIS_TAG" +echo -e "Travis tag: '$buildTag'" + +if [ "$buildTag" == "" ]; then + buildTag="snapshot" +else + buildTag="${buildTag:1}" +fi + +echo -e "JavaDocs pushback for tag: $buildTag" + +# check if the token is actually there +if [ "$GITHUB_TOKEN" == "" ]; then + echo -e "No access to GitHub, skipping JavaDocs pushback." + exit 0 +fi + +# prepare the git information +git config --global user.email "travis@travis-ci.org" +git config --global user.name "Travis CI" + +# setup the remote +echo -e "Adding the target repository to git" +git remote add origin-pages https://${GITHUB_TOKEN}@${targetRepo} > /dev/null 2>&1 + +# stash changes due to chmod +echo -e "Stashing any local non-ignored changes" +git stash + +# get the gh-pages +echo -e "Update branches and checking out gh-pages" +git fetch --all +git branch -a +git checkout -b gh-pages origin-pages/gh-pages + +# releases should update 2 extra locations +if [ "$buildTag" != "snapshot" ]; then + # for releases, add a new directory with the new version + # and carefully replace the others + + # 1.) main javadoc + # ---------------- + # remove the io subdir + echo -e "Removing javadoc/io" + rm -r javadoc/io + + # remove the html files + echo -e "Removing javadoc/*.html" + rm javadoc/*.html + + # copy the new doc + echo -e "Copying to javadoc/" + yes | cp -rf ./build/docs/javadoc/ . + + # 2.) 2.x javadoc + # remove the io subdir + echo -e "Removing 2.x/javadoc/io" + rm -r 2.x/javadoc/io + + # remove the html files + echo -e "Removing 2.x/javadoc/*.html" + rm 2.x/javadoc/*.html + + # copy the new doc + echo -e "Copying to 2.x/javadoc/" + yes | cp -rf ./build/docs/javadoc/ 2.x/ +fi + +# 3.) create a version/snapshot specific copy of the docs +# clear the existing tag +echo -e "Removing to 2.x/javadoc/${buildTag}" +rm -r 2.x/javadoc/${buildTag} + +# copy the new doc +echo -e "Copying to 2.x/javadoc/${buildTag}" +yes | cp -rf ./build/docs/javadoc/ 2.x/javadoc/${buildTag}/ + + +# stage all changed and new files +echo -e "Staging new files" +git add *.html +git add *.css +git add *.js +git add *package-list* + +# remove tracked but deleted files +echo -e "Removing deleted files" +git add -u + +# commit all +echo -e "commit Travis build: $TRAVIS_BUILD_NUMBER for $buildTag" +git commit --message "Travis build: $TRAVIS_BUILD_NUMBER for $buildTag" + +# debug file list +#find -name "*.html" + +# push it +echo -e "Pushing back changes." +git push --quiet --set-upstream origin-pages gh-pages + + +# we are done +echo -e "JavaDocs pushback complete." \ No newline at end of file diff --git a/gradle/stylesheet.css b/gradle/stylesheet.css index 7f1f6e733b..60f1d665bf 100644 --- a/gradle/stylesheet.css +++ b/gradle/stylesheet.css @@ -2,16 +2,19 @@ /* Overall document style */ + +@import url('resources/fonts/dejavu.css'); + body { background-color:#ffffff; color:#353833; - font-family:Arial, Helvetica, sans-serif; - font-size:76%; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; margin:0; } a:link, a:visited { text-decoration:none; - color:#4c6b87; + color:#4A6782; } a:hover, a:focus { text-decoration:none; @@ -19,7 +22,7 @@ a:hover, a:focus { } a:active { text-decoration:none; - color:#4c6b87; + color:#4A6782; } a[name] { color:#353833; @@ -29,41 +32,51 @@ a[name]:hover { color:#353833; } pre { - font-size:1.3em; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; } h1 { - font-size:1.8em; + font-size:20px; } h2 { - font-size:1.5em; + font-size:18px; } h3 { - font-size:1.4em; + font-size:16px; + font-style:italic; } h4 { - font-size:1.3em; + font-size:13px; } h5 { - font-size:1.2em; + font-size:12px; } h6 { - font-size:1.1em; + font-size:11px; } ul { list-style-type:disc; } code, tt { - font-size:1.2em; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; } dt code { - font-size:1.2em; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; } table tr td dt code { - font-size:1.2em; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; vertical-align:top; + padding-top:4px; } sup { - font-size:.6em; + font-size:8px; } /* Document title and Copyright styles @@ -76,9 +89,9 @@ Document title and Copyright styles .aboutLanguage { float:right; padding:0px 21px; - font-size:.8em; + font-size:11px; z-index:200; - margin-top:-7px; + margin-top:-9px; } .legalCopy { margin-left:.5em; @@ -92,9 +105,6 @@ Document title and Copyright styles } .tab { background-color:#0066FF; - background-image:url(resources/titlebar.gif); - background-position:left top; - background-repeat:no-repeat; color:#ffffff; padding:8px; width:5em; @@ -104,17 +114,15 @@ Document title and Copyright styles Navigation bar styles */ .bar { - background-image:url(resources/background.gif); - background-repeat:repeat-x; + background-color:#4D7A97; color:#FFFFFF; padding:.8em .5em .4em .8em; height:auto;/*height:1.8em;*/ - font-size:1em; + font-size:11px; margin:0; } .topNav { - background-image:url(resources/background.gif); - background-repeat:repeat-x; + background-color:#4D7A97; color:#FFFFFF; float:left; padding:0; @@ -123,11 +131,11 @@ Navigation bar styles height:2.8em; padding-top:10px; overflow:hidden; + font-size:12px; } .bottomNav { margin-top:10px; - background-image:url(resources/background.gif); - background-repeat:repeat-x; + background-color:#4D7A97; color:#FFFFFF; float:left; padding:0; @@ -136,18 +144,20 @@ Navigation bar styles height:2.8em; padding-top:10px; overflow:hidden; + font-size:12px; } .subNav { background-color:#dee3e9; - border-bottom:1px solid #9eadc0; float:left; width:100%; overflow:hidden; + font-size:12px; } .subNav div { clear:left; float:left; padding:0 0 5px 6px; + text-transform:uppercase; } ul.navList, ul.subNavList { float:left; @@ -157,27 +167,33 @@ ul.navList, ul.subNavList { ul.navList li{ list-style:none; float:left; - padding:3px 6px; + padding: 5px 6px; + text-transform:uppercase; } ul.subNavList li{ list-style:none; float:left; - font-size:90%; } .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { color:#FFFFFF; text-decoration:none; + text-transform:uppercase; } .topNav a:hover, .bottomNav a:hover { text-decoration:none; color:#bb7a2a; + text-transform:uppercase; } .navBarCell1Rev { - background-image:url(resources/tab.gif); - background-color:#a88834; - color:#FFFFFF; + background-color:#F8981D; + color:#253441; margin: auto 5px; - border:1px solid #c9aa44; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; } /* Page header and footer styles @@ -191,8 +207,11 @@ Page header and footer styles margin:10px; position:relative; } +.indexHeader span{ + margin-right:15px; +} .indexHeader h1 { - font-size:1.3em; + font-size:13px; } .title { color:#2c4557; @@ -202,7 +221,7 @@ Page header and footer styles margin:5px 0 0 0; } .header ul { - margin:0 0 25px 0; + margin:0 0 15px 0; padding:0; } .footer ul { @@ -210,24 +229,22 @@ Page header and footer styles } .header ul li, .footer ul li { list-style:none; - font-size:1.2em; + font-size:13px; } /* Heading styles */ div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { background-color:#dee3e9; - border-top:1px solid #9eadc0; - border-bottom:1px solid #9eadc0; + border:1px solid #d0d9e0; margin:0 0 6px -8px; - padding:2px 5px; + padding:7px 5px; } ul.blockList ul.blockList ul.blockList li.blockList h3 { background-color:#dee3e9; - border-top:1px solid #9eadc0; - border-bottom:1px solid #9eadc0; + border:1px solid #d0d9e0; margin:0 0 6px -8px; - padding:2px 5px; + padding:7px 5px; } ul.blockList ul.blockList li.blockList h3 { padding:0; @@ -247,10 +264,10 @@ Page layout container styles .indexContainer { margin:10px; position:relative; - font-size:1.0em; + font-size:12px; } .indexContainer h2 { - font-size:1.1em; + font-size:13px; padding:0 0 3px 0; } .indexContainer ul { @@ -259,15 +276,18 @@ Page layout container styles } .indexContainer ul li { list-style:none; + padding-top:2px; } .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { - font-size:1.1em; + font-size:12px; font-weight:bold; margin:10px 0 0 0; color:#4E4E4E; } .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { - margin:10px 0 10px 20px; + /* margin:5px 0 10px 0px; */ + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } .serializedFormContainer dl.nameValue dt { margin-left:1px; @@ -306,25 +326,24 @@ ul.blockList, ul.blockListLast { } ul.blockList li.blockList, ul.blockListLast li.blockList { list-style:none; - margin-bottom:25px; + margin-bottom:15px; + line-height:1.4; } ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { padding:0px 20px 5px 10px; - border:1px solid #9eadc0; - background-color:#f9f9f9; + border:1px solid #ededed; + background-color:#f8f8f8; } ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { padding:0 0 5px 8px; background-color:#ffffff; - border:1px solid #9eadc0; - border-top:none; + border:none; } ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { margin-left:0; padding-left:0; padding-bottom:15px; border:none; - border-bottom:1px solid #9eadc0; } ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { list-style:none; @@ -338,107 +357,155 @@ table tr td dl, table tr td dl dt, table tr td dl dd { /* Table styles */ -.contentContainer table, .classUseContainer table, .constantValuesContainer table { - border-bottom:1px solid #9eadc0; - width:100%; -} -.contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table { +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { width:100%; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; } -.contentContainer .description table, .contentContainer .details table { - border-bottom:none; -} -.contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td{ - vertical-align:top; - padding-right:20px; -} -.contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast, -.contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast, -.contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne, -.contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne { - padding-right:3px; +.overviewSummary, .memberSummary { + padding:0px; } -.overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption { +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { position:relative; text-align:left; background-repeat:no-repeat; - color:#FFFFFF; + color:#253441; font-weight:bold; clear:none; overflow:hidden; padding:0px; + padding-top:10px; + padding-left:1px; margin:0px; -} -caption a:link, caption a:hover, caption a:active, caption a:visited { + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { color:#FFFFFF; } -.overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span { +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { white-space:nowrap; - padding-top:8px; - padding-left:8px; - display:block; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; float:left; - background-image:url(resources/titlebar.gif); - height:18px; + background-color:#F8981D; + border: none; + height:16px; } -.overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd { - width:10px; - background-image:url(resources/titlebar_end.gif); - background-repeat:no-repeat; - background-position:top right; - position:relative; +.memberSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; float:left; + background-color:#F8981D; + height:16px; } -ul.blockList ul.blockList li.blockList table { - margin:0 0 12px 0px; - width:100%; +.memberSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; } -.tableSubHeadingColor { - background-color: #EEEEFF; +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; } -.altColor { - background-color:#eeeeef; +.memberSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; } -.rowColor { - background-color:#ffffff; +.memberSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; + } -.overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td { +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { text-align:left; - padding:3px 3px 3px 7px; + padding:0px 0px 12px 10px; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; } -th.colFirst, th.colLast, th.colOne, .constantValuesContainer th { +th.colFirst, th.colLast, th.colOne, .constantsSummary th { background:#dee3e9; - border-top:1px solid #9eadc0; - border-bottom:1px solid #9eadc0; text-align:left; - padding:3px 3px 3px 7px; -} -td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { - font-weight:bold; + padding:8px 3px 3px 7px; } td.colFirst, th.colFirst { - border-left:1px solid #9eadc0; white-space:nowrap; + font-size:13px; } td.colLast, th.colLast { - border-right:1px solid #9eadc0; + font-size:13px; } td.colOne, th.colOne { - border-right:1px solid #9eadc0; - border-left:1px solid #9eadc0; + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.useSummary td.colFirst, .useSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width:25%; + vertical-align:top; } -table.overviewSummary { - padding:0px; - margin-left:0px; +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; } -table.overviewSummary td.colFirst, table.overviewSummary th.colFirst, -table.overviewSummary td.colOne, table.overviewSummary th.colOne { - width:25%; - vertical-align:middle; +.tableSubHeadingColor { + background-color:#EEEEFF; } -table.packageSummary td.colFirst, table.overviewSummary th.colFirst { - width:25%; - vertical-align:middle; +.altColor { + background-color:#FFFFFF; +} +.rowColor { + background-color:#EEEEEF; } /* Content styles @@ -453,6 +520,23 @@ Content styles .docSummary { padding:0; } + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} + +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} + +td.colLast div { + padding-top:0px; +} + +td.colLast a { + padding-bottom:3px; +} /* Formatting effect styles */ @@ -463,12 +547,27 @@ Formatting effect styles h1.hidden { visibility:hidden; overflow:hidden; - font-size:.9em; + font-size:10px; } .block { display:block; - margin:3px 0 0 0; + margin:3px 10px 2px 0px; + color:#474747; } -.strong { +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { font-weight:bold; -} \ No newline at end of file +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom:0px; +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d3b83982b9..ed88a042a2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d19bad0fda..0e680f3759 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Jun 28 11:19:41 CEST 2016 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-4.3.1-bin.zip diff --git a/gradlew b/gradlew index 27309d9231..cccdd3d517 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -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 "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# 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" "$@" diff --git a/gradlew.bat b/gradlew.bat index 832fdb6079..f9553162f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @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 diff --git a/src/jmh/java/io/reactivex/BinaryFlatMapPerf.java b/src/jmh/java/io/reactivex/BinaryFlatMapPerf.java new file mode 100644 index 0000000000..45b4ee19ae --- /dev/null +++ b/src/jmh/java/io/reactivex/BinaryFlatMapPerf.java @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.Observable; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class BinaryFlatMapPerf { + @Param({ "1", "1000", "1000000" }) + public int times; + + Flowable<Integer> singleFlatMapPublisher; + + Flowable<Integer> singleFlatMapHidePublisher; + + Flowable<Integer> singleFlattenAsPublisher; + + Flowable<Integer> maybeFlatMapPublisher; + + Flowable<Integer> maybeFlatMapHidePublisher; + + Flowable<Integer> maybeFlattenAsPublisher; + + Flowable<Integer> completableFlatMapPublisher; + + Flowable<Integer> completableFlattenAsPublisher; + + Observable<Integer> singleFlatMapObservable; + + Observable<Integer> singleFlatMapHideObservable; + + Observable<Integer> singleFlattenAsObservable; + + Observable<Integer> maybeFlatMapObservable; + + Observable<Integer> maybeFlatMapHideObservable; + + Observable<Integer> maybeFlattenAsObservable; + + Observable<Integer> completableFlatMapObservable; + + Observable<Integer> completableFlattenAsObservable; + + @Setup + public void setup() { + + // -------------------------------------------------------------------------- + + final Integer[] array = new Integer[times]; + Arrays.fill(array, 777); + + final List<Integer> list = Arrays.asList(array); + + final Flowable<Integer> arrayFlowable = Flowable.fromArray(array); + final Flowable<Integer> arrayFlowableHide = Flowable.fromArray(array).hide(); + final Flowable<Integer> listFlowable = Flowable.fromIterable(list); + + final Observable<Integer> arrayObservable = Observable.fromArray(array); + final Observable<Integer> arrayObservableHide = Observable.fromArray(array).hide(); + final Observable<Integer> listObservable = Observable.fromIterable(list); + + // -------------------------------------------------------------------------- + + singleFlatMapPublisher = Single.just(1).flatMapPublisher(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return arrayFlowable; + } + }); + + singleFlatMapHidePublisher = Single.just(1).flatMapPublisher(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return arrayFlowableHide; + } + }); + + singleFlattenAsPublisher = Single.just(1).flattenAsFlowable(new Function<Integer, Iterable<? extends Integer>>() { + @Override + public Iterable<? extends Integer> apply(Integer v) + throws Exception { + return list; + } + }); + + maybeFlatMapPublisher = Maybe.just(1).flatMapPublisher(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return arrayFlowable; + } + }); + + maybeFlatMapHidePublisher = Maybe.just(1).flatMapPublisher(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return arrayFlowableHide; + } + }); + + maybeFlattenAsPublisher = Maybe.just(1).flattenAsFlowable(new Function<Integer, Iterable<? extends Integer>>() { + @Override + public Iterable<? extends Integer> apply(Integer v) + throws Exception { + return list; + } + }); + + completableFlatMapPublisher = Completable.complete().andThen(listFlowable); + + completableFlattenAsPublisher = Completable.complete().andThen(arrayFlowable); + + // -------------------------------------------------------------------------- + + singleFlatMapObservable = Single.just(1).flatMapObservable(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return arrayObservable; + } + }); + + singleFlatMapHideObservable = Single.just(1).flatMapObservable(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return arrayObservableHide; + } + }); + + singleFlattenAsObservable = Single.just(1).flattenAsObservable(new Function<Integer, Iterable<? extends Integer>>() { + @Override + public Iterable<? extends Integer> apply(Integer v) + throws Exception { + return list; + } + }); + + maybeFlatMapObservable = Maybe.just(1).flatMapObservable(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return arrayObservable; + } + }); + + maybeFlatMapHideObservable = Maybe.just(1).flatMapObservable(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return arrayObservableHide; + } + }); + + maybeFlattenAsObservable = Maybe.just(1).flattenAsObservable(new Function<Integer, Iterable<? extends Integer>>() { + @Override + public Iterable<? extends Integer> apply(Integer v) + throws Exception { + return list; + } + }); + + completableFlatMapObservable = Completable.complete().andThen(listObservable); + + completableFlattenAsObservable = Completable.complete().andThen(arrayObservable); + + } + + @Benchmark + public void singleFlatMapPublisher(Blackhole bh) { + singleFlatMapPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void singleFlatMapHidePublisher(Blackhole bh) { + singleFlatMapHidePublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void singleFlattenAsPublisher(Blackhole bh) { + singleFlattenAsPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlatMapPublisher(Blackhole bh) { + maybeFlatMapPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlatMapHidePublisher(Blackhole bh) { + maybeFlatMapHidePublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlattenAsPublisher(Blackhole bh) { + maybeFlattenAsPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void completableFlatMapPublisher(Blackhole bh) { + completableFlatMapPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void completableFlattenAsPublisher(Blackhole bh) { + completableFlattenAsPublisher.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void singleFlatMapObservable(Blackhole bh) { + singleFlatMapObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void singleFlatMapHideObservable(Blackhole bh) { + singleFlatMapHideObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void singleFlattenAsObservable(Blackhole bh) { + singleFlattenAsObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlatMapObservable(Blackhole bh) { + maybeFlatMapObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlatMapHideObservable(Blackhole bh) { + maybeFlatMapHideObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void maybeFlattenAsObservable(Blackhole bh) { + maybeFlattenAsObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void completableFlatMapObservable(Blackhole bh) { + completableFlatMapObservable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void completableFlattenAsObservable(Blackhole bh) { + completableFlattenAsObservable.subscribe(new PerfConsumer(bh)); + } +} diff --git a/src/perf/java/io/reactivex/BlockingGetPerf.java b/src/jmh/java/io/reactivex/BlockingGetPerf.java similarity index 100% rename from src/perf/java/io/reactivex/BlockingGetPerf.java rename to src/jmh/java/io/reactivex/BlockingGetPerf.java diff --git a/src/perf/java/io/reactivex/BlockingPerf.java b/src/jmh/java/io/reactivex/BlockingPerf.java similarity index 100% rename from src/perf/java/io/reactivex/BlockingPerf.java rename to src/jmh/java/io/reactivex/BlockingPerf.java diff --git a/src/perf/java/io/reactivex/CallableAsyncPerf.java b/src/jmh/java/io/reactivex/CallableAsyncPerf.java similarity index 100% rename from src/perf/java/io/reactivex/CallableAsyncPerf.java rename to src/jmh/java/io/reactivex/CallableAsyncPerf.java diff --git a/src/perf/java/io/reactivex/EachTypeFlatMapPerf.java b/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java similarity index 99% rename from src/perf/java/io/reactivex/EachTypeFlatMapPerf.java rename to src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java index d8793829f1..7d150960c0 100644 --- a/src/perf/java/io/reactivex/EachTypeFlatMapPerf.java +++ b/src/jmh/java/io/reactivex/EachTypeFlatMapPerf.java @@ -86,10 +86,12 @@ public Single<Integer> apply(Integer v) { public void bpRange(Blackhole bh) { bpRange.subscribe(new PerfSubscriber(bh)); } + @Benchmark public void bpRangeMapJust(Blackhole bh) { bpRangeMapJust.subscribe(new PerfSubscriber(bh)); } + @Benchmark public void bpRangeMapRange(Blackhole bh) { bpRangeMapRange.subscribe(new PerfSubscriber(bh)); @@ -99,10 +101,12 @@ public void bpRangeMapRange(Blackhole bh) { public void nbpRange(Blackhole bh) { nbpRange.subscribe(new PerfObserver(bh)); } + @Benchmark public void nbpRangeMapJust(Blackhole bh) { nbpRangeMapJust.subscribe(new PerfObserver(bh)); } + @Benchmark public void nbpRangeMapRange(Blackhole bh) { nbpRangeMapRange.subscribe(new PerfObserver(bh)); @@ -112,6 +116,7 @@ public void nbpRangeMapRange(Blackhole bh) { public void singleJust(Blackhole bh) { singleJust.subscribe(new LatchedSingleObserver<Integer>(bh)); } + @Benchmark public void singleJustMapJust(Blackhole bh) { singleJustMapJust.subscribe(new LatchedSingleObserver<Integer>(bh)); diff --git a/src/perf/java/io/reactivex/FlatMapJustPerf.java b/src/jmh/java/io/reactivex/FlatMapJustPerf.java similarity index 100% rename from src/perf/java/io/reactivex/FlatMapJustPerf.java rename to src/jmh/java/io/reactivex/FlatMapJustPerf.java diff --git a/src/perf/java/io/reactivex/FlattenCrossMapPerf.java b/src/jmh/java/io/reactivex/FlattenCrossMapPerf.java similarity index 100% rename from src/perf/java/io/reactivex/FlattenCrossMapPerf.java rename to src/jmh/java/io/reactivex/FlattenCrossMapPerf.java diff --git a/src/perf/java/io/reactivex/FlattenJustPerf.java b/src/jmh/java/io/reactivex/FlattenJustPerf.java similarity index 100% rename from src/perf/java/io/reactivex/FlattenJustPerf.java rename to src/jmh/java/io/reactivex/FlattenJustPerf.java diff --git a/src/jmh/java/io/reactivex/FlattenRangePerf.java b/src/jmh/java/io/reactivex/FlattenRangePerf.java new file mode 100644 index 0000000000..1d2fe51546 --- /dev/null +++ b/src/jmh/java/io/reactivex/FlattenRangePerf.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlattenRangePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int times; + + Flowable<Integer> flowable; + + Observable<Integer> observable; + + @Setup + public void setup() { + Integer[] array = new Integer[times]; + Arrays.fill(array, 777); + + final Iterable<Integer> list = Arrays.asList(1, 2); + + flowable = Flowable.fromArray(array).flatMapIterable(new Function<Integer, Iterable<Integer>>() { + @Override + public Iterable<Integer> apply(Integer v) throws Exception { + return list; + } + }); + + observable = Observable.fromArray(array).flatMapIterable(new Function<Integer, Iterable<Integer>>() { + @Override + public Iterable<Integer> apply(Integer v) throws Exception { + return list; + } + }); + } + + @Benchmark + public void flowable(Blackhole bh) { + flowable.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void observable(Blackhole bh) { + observable.subscribe(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/FlowableFlatMapCompletableAsyncPerf.java b/src/jmh/java/io/reactivex/FlowableFlatMapCompletableAsyncPerf.java new file mode 100644 index 0000000000..6dd4dcd263 --- /dev/null +++ b/src/jmh/java/io/reactivex/FlowableFlatMapCompletableAsyncPerf.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.functions.Action; +import io.reactivex.internal.functions.Functions; +import io.reactivex.schedulers.Schedulers; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapCompletableAsyncPerf implements Action { + + @Param({"1", "10", "100", "1000", "10000", "100000", "1000000"}) + int items; + + @Param({"1", "8", "32", "128", "256"}) + int maxConcurrency; + + @Param({"1", "10", "100", "1000"}) + int work; + + Completable flatMapCompletable; + + Flowable<Object> flatMap; + + @Override + public void run() throws Exception { + Blackhole.consumeCPU(work); + } + + @Setup + public void setup() { + Integer[] array = new Integer[items]; + Arrays.fill(array, 777); + + flatMapCompletable = Flowable.fromArray(array) + .flatMapCompletable(Functions.justFunction(Completable.fromAction(this).subscribeOn(Schedulers.computation())), false, maxConcurrency); + + flatMap = Flowable.fromArray(array) + .flatMap(Functions.justFunction(Completable.fromAction(this).subscribeOn(Schedulers.computation()).toFlowable()), false, maxConcurrency); + } + +// @Benchmark + public Object flatMap(Blackhole bh) { + return flatMap.subscribeWith(new PerfAsyncConsumer(bh)).await(items); + } + + @Benchmark + public Object flatMapCompletable(Blackhole bh) { + return flatMapCompletable.subscribeWith(new PerfAsyncConsumer(bh)).await(items); + } +} diff --git a/src/jmh/java/io/reactivex/FlowableFlatMapCompletableSyncPerf.java b/src/jmh/java/io/reactivex/FlowableFlatMapCompletableSyncPerf.java new file mode 100644 index 0000000000..047bad30a3 --- /dev/null +++ b/src/jmh/java/io/reactivex/FlowableFlatMapCompletableSyncPerf.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.internal.functions.Functions; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapCompletableSyncPerf { + + @Param({"1", "10", "100", "1000", "10000", "100000", "1000000"}) + int items; + + @Param({"1", "8", "32", "128", "256"}) + int maxConcurrency; + + Completable flatMapCompletable; + + Flowable<Object> flatMap; + + @Setup + public void setup() { + Integer[] array = new Integer[items]; + Arrays.fill(array, 777); + + flatMapCompletable = Flowable.fromArray(array) + .flatMapCompletable(Functions.justFunction(Completable.complete()), false, maxConcurrency); + + flatMap = Flowable.fromArray(array) + .flatMap(Functions.justFunction(Completable.complete().toFlowable()), false, maxConcurrency); + } + + @Benchmark + public Object flatMap(Blackhole bh) { + return flatMap.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flatMapCompletable(Blackhole bh) { + return flatMapCompletable.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/perf/java/io/reactivex/InputWithIncrementingInteger.java b/src/jmh/java/io/reactivex/InputWithIncrementingInteger.java similarity index 97% rename from src/perf/java/io/reactivex/InputWithIncrementingInteger.java rename to src/jmh/java/io/reactivex/InputWithIncrementingInteger.java index 8a784de957..b5ce98e407 100644 --- a/src/perf/java/io/reactivex/InputWithIncrementingInteger.java +++ b/src/jmh/java/io/reactivex/InputWithIncrementingInteger.java @@ -94,7 +94,7 @@ public void subscribe(Subscriber<? super Integer> s) { } public Iterable<Integer> iterable; - public Flowable<Integer> observable; + public Flowable<Integer> flowable; public Flowable<Integer> firehose; public Blackhole bh; @@ -104,7 +104,7 @@ public void subscribe(Subscriber<? super Integer> s) { public void setup(final Blackhole bh) { this.bh = bh; final int size = getSize(); - observable = Flowable.range(0, size); + flowable = Flowable.range(0, size); firehose = Flowable.unsafeCreate(new IncrementingPublisher(size)); iterable = new IncrementingIterable(size); diff --git a/src/perf/java/io/reactivex/JustAsyncPerf.java b/src/jmh/java/io/reactivex/JustAsyncPerf.java similarity index 100% rename from src/perf/java/io/reactivex/JustAsyncPerf.java rename to src/jmh/java/io/reactivex/JustAsyncPerf.java diff --git a/src/perf/java/io/reactivex/LatchedSingleObserver.java b/src/jmh/java/io/reactivex/LatchedSingleObserver.java similarity index 99% rename from src/perf/java/io/reactivex/LatchedSingleObserver.java rename to src/jmh/java/io/reactivex/LatchedSingleObserver.java index 02e74c463e..59b980a0eb 100644 --- a/src/perf/java/io/reactivex/LatchedSingleObserver.java +++ b/src/jmh/java/io/reactivex/LatchedSingleObserver.java @@ -26,15 +26,18 @@ public LatchedSingleObserver(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } + @Override public void onSubscribe(Disposable d) { } + @Override public void onSuccess(T value) { bh.consume(value); cdl.countDown(); } + @Override public void onError(Throwable e) { e.printStackTrace(); diff --git a/src/jmh/java/io/reactivex/MemoryPerf.java b/src/jmh/java/io/reactivex/MemoryPerf.java new file mode 100644 index 0000000000..2a625c2149 --- /dev/null +++ b/src/jmh/java/io/reactivex/MemoryPerf.java @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.lang.management.ManagementFactory; +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscription; + +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.*; + +/** + * Measure various prepared flows about their memory usage and print the result + * in a JMH compatible format; run {@link #main(String[])}. + */ +public final class MemoryPerf { + + private MemoryPerf() { } + + static long memoryUse() { + return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + } + + static final class MyRx2Subscriber implements FlowableSubscriber<Object> { + + org.reactivestreams.Subscription upstream; + + @Override + public void onSubscribe(Subscription s) { + this.upstream = s; + } + + @Override + public void onComplete() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Object t) { + + } + } + + static final class MyRx2Observer implements io.reactivex.Observer<Object>, io.reactivex.SingleObserver<Object>, + io.reactivex.MaybeObserver<Object>, io.reactivex.CompletableObserver { + + Disposable upstream; + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + } + + @Override + public void onComplete() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Object t) { + + } + + @Override + public void onSuccess(Object value) { + + } + } + static <U> void checkMemory(Callable<U> item, String name, String typeLib) throws Exception { + checkMemory(item, name, typeLib, 1000000); + } + + static <U> void checkMemory(Callable<U> item, String name, String typeLib, int n) throws Exception { + // make sure classes are initialized + item.call(); + + Object[] array = new Object[n]; + + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long before = memoryUse(); + + for (int i = 0; i < n; i++) { + array[i] = item.call(); + } + + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long after = memoryUse(); + + double use = Math.max(0.0, (after - before) / 1024.0 / 1024.0); + + System.out.print(name); + System.out.print(" "); + System.out.print(typeLib); + System.out.print(" thrpt "); + System.out.print(n); + System.out.printf(" %.3f 0.000 MB%n", use); + + if (array.hashCode() == 1) { + System.out.print(""); + } + + array = null; + item = null; + + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + } + + public static void main(String[] args) throws Exception { + + System.out.println("Benchmark (lib-type) Mode Cnt Score Error Units"); + + // --------------------------------------------------------------------------------------------------------------------- + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.just(1); + } + }, "just", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10); + } + }, "range", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.empty(); + } + }, "empty", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return 1; + } + }); + } + }, "fromCallable", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new MyRx2Observer(); + } + }, "consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new io.reactivex.observers.TestObserver<Object>(); + } + }, "test-consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.just(1).subscribeWith(new MyRx2Observer()); + } + }, "just+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).subscribeWith(new MyRx2Observer()); + } + }, "range+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + return v; + } + }).subscribeWith(new MyRx2Observer()); + } + }, "range+map+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + return v; + } + }).filter(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return true; + } + }).subscribeWith(new MyRx2Observer()); + } + }, "range+map+filter+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).subscribeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Observer()); + } + }, "range+subscribeOn+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).observeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Observer()); + } + }, "range+observeOn+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Observable.range(1, 10).subscribeOn(io.reactivex.schedulers.Schedulers.computation()).observeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Observer()); + } + }, "range+subscribeOn+observeOn+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.AsyncSubject.create(); + } + }, "Async", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.PublishSubject.create(); + } + }, "Publish", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.ReplaySubject.create(); + } + }, "Replay", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.BehaviorSubject.create(); + } + }, "Behavior", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.UnicastSubject.create(); + } + }, "Unicast", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.AsyncSubject.create().subscribeWith(new MyRx2Observer()); + } + }, "Async+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.PublishSubject.create().subscribeWith(new MyRx2Observer()); + } + }, "Publish+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.ReplaySubject.create().subscribeWith(new MyRx2Observer()); + } + }, "Replay+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.BehaviorSubject.create().subscribeWith(new MyRx2Observer()); + } + }, "Behavior+consumer", "Rx2Observable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.subjects.UnicastSubject.create().subscribeWith(new MyRx2Observer()); + } + }, "Unicast+consumer", "Rx2Observable"); + + // --------------------------------------------------------------------------------------------------------------------- + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.just(1); + } + }, "just", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10); + } + }, "range", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.empty(); + } + }, "empty", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.empty(); + } + }, "empty", "Rx2Flowable", 10000000); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return 1; + } + }); + } + }, "fromCallable", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new MyRx2Subscriber(); + } + }, "consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new io.reactivex.observers.TestObserver<Object>(); + } + }, "test-consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.just(1).subscribeWith(new MyRx2Subscriber()); + } + }, "just+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).subscribeWith(new MyRx2Subscriber()); + } + }, "range+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + return v; + } + }).subscribeWith(new MyRx2Subscriber()); + } + }, "range+map+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + return v; + } + }).filter(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return true; + } + }).subscribeWith(new MyRx2Subscriber()); + } + }, "range+map+filter+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).subscribeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Subscriber()); + } + }, "range+subscribeOn+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).observeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Subscriber()); + } + }, "range+observeOn+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.Flowable.range(1, 10).subscribeOn(io.reactivex.schedulers.Schedulers.computation()).observeOn(io.reactivex.schedulers.Schedulers.computation()).subscribeWith(new MyRx2Subscriber()); + } + }, "range+subscribeOn+observeOn+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.AsyncProcessor.create(); + } + }, "Async", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.PublishProcessor.create(); + } + }, "Publish", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.ReplayProcessor.create(); + } + }, "Replay", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.BehaviorProcessor.create(); + } + }, "Behavior", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.UnicastProcessor.create(); + } + }, "Unicast", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.AsyncProcessor.create().subscribeWith(new MyRx2Subscriber()); + } + }, "Async+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.PublishProcessor.create().subscribeWith(new MyRx2Subscriber()); + } + }, "Publish+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.ReplayProcessor.create().subscribeWith(new MyRx2Subscriber()); + } + }, "Replay+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.BehaviorProcessor.create().subscribeWith(new MyRx2Subscriber()); + } + }, "Behavior+consumer", "Rx2Flowable"); + + checkMemory(new Callable<Object>() { + @Override + public Object call() throws Exception { + return io.reactivex.processors.UnicastProcessor.create().subscribeWith(new MyRx2Subscriber()); + } + }, "Unicast+consumer", "Rx2Flowable"); + + // --------------------------------------------------------------------------------------------------------------------- + } +} diff --git a/src/perf/java/io/reactivex/ObservableFlatMapPerf.java b/src/jmh/java/io/reactivex/ObservableFlatMapPerf.java similarity index 100% rename from src/perf/java/io/reactivex/ObservableFlatMapPerf.java rename to src/jmh/java/io/reactivex/ObservableFlatMapPerf.java diff --git a/src/perf/java/io/reactivex/OperatorFlatMapPerf.java b/src/jmh/java/io/reactivex/OperatorFlatMapPerf.java similarity index 93% rename from src/perf/java/io/reactivex/OperatorFlatMapPerf.java rename to src/jmh/java/io/reactivex/OperatorFlatMapPerf.java index c8ab4aacdf..c1f0ddc11a 100644 --- a/src/perf/java/io/reactivex/OperatorFlatMapPerf.java +++ b/src/jmh/java/io/reactivex/OperatorFlatMapPerf.java @@ -42,7 +42,7 @@ public int getSize() { @Benchmark public void flatMapIntPassthruSync(Input input) throws InterruptedException { - input.observable.flatMap(new Function<Integer, Publisher<Integer>>() { + input.flowable.flatMap(new Function<Integer, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Integer v) { return Flowable.just(v); @@ -53,7 +53,7 @@ public Publisher<Integer> apply(Integer v) { @Benchmark public void flatMapIntPassthruAsync(Input input) throws InterruptedException { PerfSubscriber latchedObserver = input.newLatchedObserver(); - input.observable.flatMap(new Function<Integer, Publisher<Integer>>() { + input.flowable.flatMap(new Function<Integer, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Integer i) { return Flowable.just(i).subscribeOn(Schedulers.computation()); @@ -71,7 +71,7 @@ public void flatMapTwoNestedSync(final Input input) throws InterruptedException Flowable.range(1, 2).flatMap(new Function<Integer, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Integer i) { - return input.observable; + return input.flowable; } }).subscribe(input.newSubscriber()); } diff --git a/src/perf/java/io/reactivex/OperatorMergePerf.java b/src/jmh/java/io/reactivex/OperatorMergePerf.java similarity index 96% rename from src/perf/java/io/reactivex/OperatorMergePerf.java rename to src/jmh/java/io/reactivex/OperatorMergePerf.java index 29de58de2a..97006c163a 100644 --- a/src/perf/java/io/reactivex/OperatorMergePerf.java +++ b/src/jmh/java/io/reactivex/OperatorMergePerf.java @@ -68,7 +68,7 @@ public Flowable<Integer> apply(Integer i) { @Benchmark public void mergeNSyncStreamsOfN(final InputThousand input) throws InterruptedException { - Flowable<Flowable<Integer>> os = input.observable.map(new Function<Integer, Flowable<Integer>>() { + Flowable<Flowable<Integer>> os = input.flowable.map(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer i) { return Flowable.range(0, input.size); @@ -85,7 +85,7 @@ public Flowable<Integer> apply(Integer i) { @Benchmark public void mergeNAsyncStreamsOfN(final InputThousand input) throws InterruptedException { - Flowable<Flowable<Integer>> os = input.observable.map(new Function<Integer, Flowable<Integer>>() { + Flowable<Flowable<Integer>> os = input.flowable.map(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer i) { return Flowable.range(0, input.size).subscribeOn(Schedulers.computation()); diff --git a/src/perf/java/io/reactivex/PerfAsyncConsumer.java b/src/jmh/java/io/reactivex/PerfAsyncConsumer.java similarity index 95% rename from src/perf/java/io/reactivex/PerfAsyncConsumer.java rename to src/jmh/java/io/reactivex/PerfAsyncConsumer.java index 8db609b4b5..0b99202afc 100644 --- a/src/perf/java/io/reactivex/PerfAsyncConsumer.java +++ b/src/jmh/java/io/reactivex/PerfAsyncConsumer.java @@ -68,8 +68,9 @@ public void onComplete() { /** * Wait for the terminal signal. * @param count if less than 1001, a spin-wait is used + * @return this */ - public void await(int count) { + public PerfAsyncConsumer await(int count) { if (count <= 1000) { while (getCount() != 0) { } } else { @@ -79,6 +80,7 @@ public void await(int count) { throw new RuntimeException(ex); } } + return this; } } diff --git a/src/main/java/io/reactivex/internal/subscribers/FullArbiterSubscriber.java b/src/jmh/java/io/reactivex/PerfBoundedSubscriber.java similarity index 53% rename from src/main/java/io/reactivex/internal/subscribers/FullArbiterSubscriber.java rename to src/jmh/java/io/reactivex/PerfBoundedSubscriber.java index e145489296..39cc6f551b 100644 --- a/src/main/java/io/reactivex/internal/subscribers/FullArbiterSubscriber.java +++ b/src/jmh/java/io/reactivex/PerfBoundedSubscriber.java @@ -11,47 +11,46 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex.internal.subscribers; +package io.reactivex; -import org.reactivestreams.Subscription; +import java.util.concurrent.CountDownLatch; -import io.reactivex.FlowableSubscriber; -import io.reactivex.internal.subscriptions.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Subscription; /** - * Subscriber that communicates with a FullArbiter. - * - * @param <T> the value type + * Performance subscriber with a one-time request from the upstream. */ -public final class FullArbiterSubscriber<T> implements FlowableSubscriber<T> { - final FullArbiter<T> arbiter; +public class PerfBoundedSubscriber extends CountDownLatch implements FlowableSubscriber<Object> { + + final Blackhole bh; - Subscription s; + final long request; - public FullArbiterSubscriber(FullArbiter<T> arbiter) { - this.arbiter = arbiter; + public PerfBoundedSubscriber(Blackhole bh, long request) { + super(1); + this.bh = bh; + this.request = request; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - arbiter.setSubscription(s); - } + s.request(request); } @Override - public void onNext(T t) { - arbiter.onNext(t, s); + public void onComplete() { + countDown(); } @Override - public void onError(Throwable t) { - arbiter.onError(t, s); + public void onError(Throwable e) { + countDown(); } @Override - public void onComplete() { - arbiter.onComplete(s); + public void onNext(Object t) { + bh.consume(t); } + } diff --git a/src/perf/java/io/reactivex/PerfConsumer.java b/src/jmh/java/io/reactivex/PerfConsumer.java similarity index 100% rename from src/perf/java/io/reactivex/PerfConsumer.java rename to src/jmh/java/io/reactivex/PerfConsumer.java diff --git a/src/perf/java/io/reactivex/PerfInteropConsumer.java b/src/jmh/java/io/reactivex/PerfInteropConsumer.java similarity index 100% rename from src/perf/java/io/reactivex/PerfInteropConsumer.java rename to src/jmh/java/io/reactivex/PerfInteropConsumer.java diff --git a/src/perf/java/io/reactivex/PerfObserver.java b/src/jmh/java/io/reactivex/PerfObserver.java similarity index 99% rename from src/perf/java/io/reactivex/PerfObserver.java rename to src/jmh/java/io/reactivex/PerfObserver.java index 33548155ba..8de241e6a0 100644 --- a/src/perf/java/io/reactivex/PerfObserver.java +++ b/src/jmh/java/io/reactivex/PerfObserver.java @@ -26,19 +26,23 @@ public PerfObserver(Blackhole bh) { this.bh = bh; this.cdl = new CountDownLatch(1); } + @Override public void onSubscribe(Disposable d) { } + @Override public void onNext(Object value) { bh.consume(value); } + @Override public void onError(Throwable e) { e.printStackTrace(); cdl.countDown(); } + @Override public void onComplete() { cdl.countDown(); diff --git a/src/perf/java/io/reactivex/PerfSubscriber.java b/src/jmh/java/io/reactivex/PerfSubscriber.java similarity index 100% rename from src/perf/java/io/reactivex/PerfSubscriber.java rename to src/jmh/java/io/reactivex/PerfSubscriber.java diff --git a/src/jmh/java/io/reactivex/PublishProcessorPerf.java b/src/jmh/java/io/reactivex/PublishProcessorPerf.java new file mode 100644 index 0000000000..d9531ab267 --- /dev/null +++ b/src/jmh/java/io/reactivex/PublishProcessorPerf.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.PublishSubject; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class PublishProcessorPerf { + + PublishProcessor<Integer> unbounded; + + PublishProcessor<Integer> bounded; + + PublishSubject<Integer> subject; + + @Setup + public void setup(Blackhole bh) { + unbounded = PublishProcessor.create(); + unbounded.subscribe(new PerfConsumer(bh)); + + bounded = PublishProcessor.create(); + bounded.subscribe(new PerfBoundedSubscriber(bh, 1000 * 1000)); + + subject = PublishSubject.create(); + subject.subscribe(new PerfConsumer(bh)); + } + + @Benchmark + public void unbounded1() { + unbounded.onNext(1); + } + + @Benchmark + public void unbounded1k() { + for (int i = 0; i < 1000; i++) { + unbounded.onNext(1); + } + } + + @Benchmark + public void unbounded1m() { + for (int i = 0; i < 1000000; i++) { + unbounded.onNext(1); + } + } + + @Benchmark + public void bounded1() { + bounded.onNext(1); + } + + @Benchmark + public void bounded1k() { + for (int i = 0; i < 1000; i++) { + bounded.onNext(1); + } + } + + @Benchmark + public void bounded1m() { + for (int i = 0; i < 1000000; i++) { + bounded.onNext(1); + } + } + + @Benchmark + public void subject1() { + subject.onNext(1); + } + + @Benchmark + public void subject1k() { + for (int i = 0; i < 1000; i++) { + subject.onNext(1); + } + } + + @Benchmark + public void subject1m() { + for (int i = 0; i < 1000000; i++) { + subject.onNext(1); + } + } +} diff --git a/src/perf/java/io/reactivex/RangePerf.java b/src/jmh/java/io/reactivex/RangePerf.java similarity index 100% rename from src/perf/java/io/reactivex/RangePerf.java rename to src/jmh/java/io/reactivex/RangePerf.java diff --git a/src/perf/java/io/reactivex/ReducePerf.java b/src/jmh/java/io/reactivex/ReducePerf.java similarity index 100% rename from src/perf/java/io/reactivex/ReducePerf.java rename to src/jmh/java/io/reactivex/ReducePerf.java diff --git a/src/perf/java/io/reactivex/RxVsStreamPerf.java b/src/jmh/java/io/reactivex/RxVsStreamPerf.java similarity index 100% rename from src/perf/java/io/reactivex/RxVsStreamPerf.java rename to src/jmh/java/io/reactivex/RxVsStreamPerf.java diff --git a/src/perf/java/io/reactivex/StrictPerf.java b/src/jmh/java/io/reactivex/StrictPerf.java similarity index 100% rename from src/perf/java/io/reactivex/StrictPerf.java rename to src/jmh/java/io/reactivex/StrictPerf.java diff --git a/src/jmh/java/io/reactivex/TakeUntilPerf.java b/src/jmh/java/io/reactivex/TakeUntilPerf.java new file mode 100644 index 0000000000..b27afa407e --- /dev/null +++ b/src/jmh/java/io/reactivex/TakeUntilPerf.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.concurrent.*; + +import org.openjdk.jmh.annotations.*; + +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.schedulers.Schedulers; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +@AuxCounters +public class TakeUntilPerf implements Consumer<Integer> { + + public volatile int items; + + static final int count = 10000; + + Flowable<Integer> flowable; + + Observable<Integer> observable; + + @Override + public void accept(Integer t) throws Exception { + items++; + } + + @Setup + public void setup() { + + flowable = Flowable.range(1, 1000 * 1000).takeUntil(Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + int c = count; + while (items < c) { } + return 1; + } + }).subscribeOn(Schedulers.single())); + + observable = Observable.range(1, 1000 * 1000).takeUntil(Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + int c = count; + while (items < c) { } + return 1; + } + }).subscribeOn(Schedulers.single())); + } + + @Benchmark + public void flowable() { + final CountDownLatch cdl = new CountDownLatch(1); + + flowable.subscribe(this, Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + cdl.countDown(); + } + }); + + while (cdl.getCount() != 0) { } + } + + @Benchmark + public void observable() { + final CountDownLatch cdl = new CountDownLatch(1); + + observable.subscribe(this, Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + cdl.countDown(); + } + }); + + while (cdl.getCount() != 0) { } + } +} diff --git a/src/perf/java/io/reactivex/ToFlowablePerf.java b/src/jmh/java/io/reactivex/ToFlowablePerf.java similarity index 99% rename from src/perf/java/io/reactivex/ToFlowablePerf.java rename to src/jmh/java/io/reactivex/ToFlowablePerf.java index 9bc41083f4..deb97a7e51 100644 --- a/src/perf/java/io/reactivex/ToFlowablePerf.java +++ b/src/jmh/java/io/reactivex/ToFlowablePerf.java @@ -78,6 +78,7 @@ public Observable<Integer> apply(Integer v) throws Exception { public Object flowable() { return flowable.blockingGet(); } + @Benchmark public Object flowableInner() { return flowableInner.blockingLast(); diff --git a/src/perf/java/io/reactivex/XMapYPerf.java b/src/jmh/java/io/reactivex/XMapYPerf.java similarity index 99% rename from src/perf/java/io/reactivex/XMapYPerf.java rename to src/jmh/java/io/reactivex/XMapYPerf.java index 389a89000f..d9e198b284 100644 --- a/src/perf/java/io/reactivex/XMapYPerf.java +++ b/src/jmh/java/io/reactivex/XMapYPerf.java @@ -94,7 +94,6 @@ public class XMapYPerf { Observable<Integer> obsFlatMapIterableAsObs0; - @Setup public void setup() { Integer[] values = new Integer[times]; @@ -158,7 +157,6 @@ public Iterable<Integer> apply(Integer v) throws Exception { } }); - flowFlatMapSingle1 = fsource.flatMapSingle(new Function<Integer, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Integer v) throws Exception { @@ -231,7 +229,6 @@ public Publisher<Integer> apply(Integer v) throws Exception { } }); - // ------------------------------------------------------------------- Observable<Integer> osource = Observable.fromArray(values); @@ -271,7 +268,6 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { } }); - obsFlatMapCompletable0 = osource.flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { diff --git a/src/perf/java/io/reactivex/parallel/ParallelPerf.java b/src/jmh/java/io/reactivex/parallel/ParallelPerf.java similarity index 98% rename from src/perf/java/io/reactivex/parallel/ParallelPerf.java rename to src/jmh/java/io/reactivex/parallel/ParallelPerf.java index 1630e82eef..24a0e85954 100644 --- a/src/perf/java/io/reactivex/parallel/ParallelPerf.java +++ b/src/jmh/java/io/reactivex/parallel/ParallelPerf.java @@ -28,7 +28,7 @@ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 5) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) -@Fork(value = 1,jvmArgsAppend = { "-XX:MaxInlineLevel=20" }) +@Fork(value = 1, jvmArgsAppend = { "-XX:MaxInlineLevel=20" }) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ParallelPerf implements Function<Integer, Integer> { diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapCompletablePerf.java new file mode 100644 index 0000000000..ab23c7e252 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapCompletablePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableConcatMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Completable flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + flowableConvert = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toFlowable(); + } + }); + + flowableDedicated = source.concatMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..b439425bd0 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybeEmptyPerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableConcatMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> concatMapToFlowableEmpty; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + concatMapToFlowableEmpty = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toFlowable(); + } + }); + + flowableDedicated = source.concatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return concatMapToFlowableEmpty.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybePerf.java new file mode 100644 index 0000000000..7f891292d2 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapMaybePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableConcatMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toFlowable(); + } + }); + + flowableDedicated = source.concatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapSinglePerf.java new file mode 100644 index 0000000000..69f0b314fe --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableConcatMapSinglePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableConcatMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toFlowable(); + } + }); + + flowableDedicated = source.concatMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapCompletablePerf.java new file mode 100644 index 0000000000..4b9f37664a --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapCompletablePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Completable flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + flowableConvert = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toFlowable(); + } + }); + + flowableDedicated = source.flatMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..bf2ad4396e --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybeEmptyPerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + flowableConvert = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toFlowable(); + } + }); + + flowableDedicated = source.flatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybePerf.java new file mode 100644 index 0000000000..37c970d6cc --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapMaybePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toFlowable(); + } + }); + + flowableDedicated = source.flatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapSinglePerf.java new file mode 100644 index 0000000000..e4405932f8 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableFlatMapSinglePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableFlatMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.flatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toFlowable(); + } + }); + + flowableDedicated = source.flatMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapCompletablePerf.java new file mode 100644 index 0000000000..d0c3041101 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapCompletablePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableSwitchMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Completable flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + flowableConvert = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toFlowable(); + } + }); + + flowableDedicated = source.switchMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..217d7bdeba --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybeEmptyPerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableSwitchMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.empty(); + } + }); + + flowableConvert = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toFlowable(); + } + }); + + flowableDedicated = source.switchMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybePerf.java new file mode 100644 index 0000000000..f00690b313 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapMaybePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableSwitchMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toFlowable(); + } + }); + + flowableDedicated = source.switchMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapSinglePerf.java new file mode 100644 index 0000000000..ad5b3209f0 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/FlowableSwitchMapSinglePerf.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class FlowableSwitchMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Flowable<Integer> flowableConvert; + + Flowable<Integer> flowableDedicated; + + Flowable<Integer> flowablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Flowable<Integer> source = Flowable.fromArray(sourceArray); + + flowablePlain = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }); + + flowableConvert = source.switchMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toFlowable(); + } + }); + + flowableDedicated = source.switchMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object flowablePlain(Blackhole bh) { + return flowablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableConvert(Blackhole bh) { + return flowableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object flowableDedicated(Blackhole bh) { + return flowableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapCompletablePerf.java new file mode 100644 index 0000000000..18382f6d55 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapCompletablePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableConcatMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Completable observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + observableConvert = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toObservable(); + } + }); + + observableDedicated = source.concatMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..bc5752eeff --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybeEmptyPerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableConcatMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> concatMapToObservableEmpty; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + concatMapToObservableEmpty = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toObservable(); + } + }); + + observableDedicated = source.concatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return concatMapToObservableEmpty.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybePerf.java new file mode 100644 index 0000000000..e85e2708e4 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapMaybePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableConcatMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toObservable(); + } + }); + + observableDedicated = source.concatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapSinglePerf.java new file mode 100644 index 0000000000..090e1fde96 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableConcatMapSinglePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableConcatMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.concatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toObservable(); + } + }); + + observableDedicated = source.concatMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapCompletablePerf.java new file mode 100644 index 0000000000..10bbed676e --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapCompletablePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableFlatMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Completable observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + observableConvert = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toObservable(); + } + }); + + observableDedicated = source.flatMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..021ae89332 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybeEmptyPerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableFlatMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + observableConvert = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toObservable(); + } + }); + + observableDedicated = source.flatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybePerf.java new file mode 100644 index 0000000000..2c1eaccb00 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapMaybePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableFlatMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toObservable(); + } + }); + + observableDedicated = source.flatMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapSinglePerf.java new file mode 100644 index 0000000000..46cd554792 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableFlatMapSinglePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableFlatMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.flatMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toObservable(); + } + }); + + observableDedicated = source.flatMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapCompletablePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapCompletablePerf.java new file mode 100644 index 0000000000..7b1b9e6925 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapCompletablePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableSwitchMapCompletablePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Completable observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + observableConvert = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Completable.complete().toObservable(); + } + }); + + observableDedicated = source.switchMapCompletable(new Function<Integer, Completable>() { + @Override + public Completable apply(Integer v) + throws Exception { + return Completable.complete(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybeEmptyPerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybeEmptyPerf.java new file mode 100644 index 0000000000..77657c69cb --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybeEmptyPerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableSwitchMapMaybeEmptyPerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.empty(); + } + }); + + observableConvert = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.<Integer>empty().toObservable(); + } + }); + + observableDedicated = source.switchMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybePerf.java new file mode 100644 index 0000000000..5e80b84e36 --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapMaybePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableSwitchMapMaybePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v).toObservable(); + } + }); + + observableDedicated = source.switchMapMaybe(new Function<Integer, Maybe<? extends Integer>>() { + @Override + public Maybe<? extends Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapSinglePerf.java b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapSinglePerf.java new file mode 100644 index 0000000000..fcca79aa8b --- /dev/null +++ b/src/jmh/java/io/reactivex/xmapz/ObservableSwitchMapSinglePerf.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.xmapz; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 5) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) +@Fork(value = 1) +@State(Scope.Thread) +public class ObservableSwitchMapSinglePerf { + @Param({ "1", "10", "100", "1000", "10000", "100000", "1000000" }) + public int count; + + Observable<Integer> observableConvert; + + Observable<Integer> observableDedicated; + + Observable<Integer> observablePlain; + + @Setup + public void setup() { + Integer[] sourceArray = new Integer[count]; + Arrays.fill(sourceArray, 777); + + Observable<Integer> source = Observable.fromArray(sourceArray); + + observablePlain = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Observable.just(v); + } + }); + + observableConvert = source.switchMap(new Function<Integer, Observable<? extends Integer>>() { + @Override + public Observable<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v).toObservable(); + } + }); + + observableDedicated = source.switchMapSingle(new Function<Integer, Single<? extends Integer>>() { + @Override + public Single<? extends Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }); + } + + @Benchmark + public Object observablePlain(Blackhole bh) { + return observablePlain.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableConvert(Blackhole bh) { + return observableConvert.subscribeWith(new PerfConsumer(bh)); + } + + @Benchmark + public Object observableDedicated(Blackhole bh) { + return observableDedicated.subscribeWith(new PerfConsumer(bh)); + } +} diff --git a/src/main/java/io/reactivex/Completable.java b/src/main/java/io/reactivex/Completable.java index 2c7f22bb75..1994632307 100644 --- a/src/main/java/io/reactivex/Completable.java +++ b/src/main/java/io/reactivex/Completable.java @@ -24,24 +24,90 @@ import io.reactivex.internal.fuseable.*; import io.reactivex.internal.observers.*; import io.reactivex.internal.operators.completable.*; -import io.reactivex.internal.operators.flowable.FlowableDelaySubscriptionOther; import io.reactivex.internal.operators.maybe.*; -import io.reactivex.internal.operators.observable.ObservableDelaySubscriptionOther; -import io.reactivex.internal.operators.single.SingleDelayWithCompletable; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.single.*; import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; /** - * Represents a deferred computation without any value but only indication for completion or exception. + * The {@code Completable} class represents a deferred computation without any value but + * only indication for completion or exception. + * <p> + * {@code Completable} behaves similarly to {@link Observable} except that it can only emit either + * a completion or error signal (there is no {@code onNext} or {@code onSuccess} as with the other + * reactive types). + * <p> + * The {@code Completable} class implements the {@link CompletableSource} base interface and the default consumer + * type it interacts with is the {@link CompletableObserver} via the {@link #subscribe(CompletableObserver)} method. + * The {@code Completable} operates with the following sequential protocol: + * <pre><code> + * onSubscribe (onError | onComplete)? + * </code></pre> + * <p> + * Note that as with the {@code Observable} protocol, {@code onError} and {@code onComplete} are mutually exclusive events. + * <p> + * Like {@link Observable}, a running {@code Completable} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link SingleObserver#onSubscribe}. + * <p> + * Like an {@code Observable}, a {@code Completable} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Completable} instances returned by the methods of this class are <em>cold</em> + * and there is a standard <em>hot</em> implementation in the form of a subject: + * {@link io.reactivex.subjects.CompletableSubject CompletableSubject}. + * <p> + * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + * <p> + * <img width="640" height="577" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.png" alt=""> + * <p> + * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. + * <p> + * Example: + * <pre><code> + * Disposable d = Completable.complete() + * .delay(10, TimeUnit.SECONDS, Schedulers.io()) + * .subscribeWith(new DisposableCompletableObserver() { + * @Override + * public void onStart() { + * System.out.println("Started"); + * } * - * The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)? + * @Override + * public void onError(Throwable error) { + * error.printStackTrace(); + * } + * + * @Override + * public void onComplete() { + * System.out.println("Done!"); + * } + * }); + * + * Thread.sleep(5000); + * + * d.dispose(); + * </code></pre> + * <p> + * Note that by design, subscriptions via {@link #subscribe(CompletableObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(CompletableObserver)} method) and it is the + * responsibility of the implementor of the {@code CompletableObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableCompletableObserver DisposableCompletableObserver} instance. + * For convenience, the {@link #subscribeWith(CompletableObserver)} method is provided as well to + * allow working with a {@code CompletableObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). + * + * @see io.reactivex.observers.DisposableCompletableObserver */ public abstract class Completable implements CompletableSource { /** * Returns a Completable which terminates as soon as one of the source Completables - * terminates (normally or with an error) and cancels all other Completables. + * terminates (normally or with an error) and disposes all other Completables. + * <p> + * <img width="640" height="518" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.ambArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -52,6 +118,7 @@ public abstract class Completable implements CompletableSource { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable ambArray(final CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -67,7 +134,9 @@ public static Completable ambArray(final CompletableSource... sources) { /** * Returns a Completable which terminates as soon as one of the source Completables - * terminates (normally or with an error) and cancels all other Completables. + * terminates (normally or with an error) and disposes all other Completables. + * <p> + * <img width="640" height="518" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.amb.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code amb} does not operate by default on a particular {@link Scheduler}.</dd> @@ -78,6 +147,7 @@ public static Completable ambArray(final CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable amb(final Iterable<? extends CompletableSource> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -87,6 +157,8 @@ public static Completable amb(final Iterable<? extends CompletableSource> source /** * Returns a Completable instance that completes immediately when subscribed to. + * <p> + * <img width="640" height="472" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.complete.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code complete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -94,6 +166,7 @@ public static Completable amb(final Iterable<? extends CompletableSource> source * @return a Completable instance that completes immediately */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable complete() { return RxJavaPlugins.onAssembly(CompletableEmpty.INSTANCE); @@ -101,6 +174,8 @@ public static Completable complete() { /** * Returns a Completable which completes only when all sources complete, one after another. + * <p> + * <img width="640" height="283" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.concatArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concatArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -110,6 +185,7 @@ public static Completable complete() { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable concatArray(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -124,6 +200,8 @@ public static Completable concatArray(CompletableSource... sources) { /** * Returns a Completable which completes only when all sources complete, one after another. + * <p> + * <img width="640" height="303" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.concat.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -133,6 +211,7 @@ public static Completable concatArray(CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable concat(Iterable<? extends CompletableSource> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -142,6 +221,8 @@ public static Completable concat(Iterable<? extends CompletableSource> sources) /** * Returns a Completable which completes only when all sources complete, one after another. + * <p> + * <img width="640" height="237" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.concat.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer @@ -162,6 +243,8 @@ public static Completable concat(Publisher<? extends CompletableSource> sources) /** * Returns a Completable which completes only when all sources complete, one after another. + * <p> + * <img width="640" height="237" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.concat.pn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer @@ -175,6 +258,7 @@ public static Completable concat(Publisher<? extends CompletableSource> sources) * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) public static Completable concat(Publisher<? extends CompletableSource> sources, int prefetch) { @@ -186,6 +270,8 @@ public static Completable concat(Publisher<? extends CompletableSource> sources, /** * Provides an API (via a cold Completable) that bridges the reactive world with the callback-style world. * <p> + * <img width="640" height="442" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.create.png" alt=""> + * <p> * Example: * <pre><code> * Completable.create(emitter -> { @@ -207,7 +293,6 @@ public static Completable concat(Publisher<? extends CompletableSource> sources, * * }); * </code></pre> - * <p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code create} does not operate by default on a particular {@link Scheduler}.</dd> @@ -218,6 +303,7 @@ public static Completable concat(Publisher<? extends CompletableSource> sources, * @see Cancellable */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable create(CompletableOnSubscribe source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -227,7 +313,9 @@ public static Completable create(CompletableOnSubscribe source) { /** * Constructs a Completable instance by wrapping the given source callback * <strong>without any safeguards; you should manage the lifecycle and response - * to downstream cancellation/dispose</strong>. + * to downstream disposal</strong>. + * <p> + * <img width="640" height="260" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.unsafeCreate.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -238,6 +326,7 @@ public static Completable create(CompletableOnSubscribe source) { * @throws NullPointerException if source is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable unsafeCreate(CompletableSource source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -249,6 +338,8 @@ public static Completable unsafeCreate(CompletableSource source) { /** * Defers the subscription to a Completable instance returned by a supplier. + * <p> + * <img width="640" height="298" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.defer.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code defer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -257,6 +348,7 @@ public static Completable unsafeCreate(CompletableSource source) { * @return the Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable defer(final Callable<? extends CompletableSource> completableSupplier) { ObjectHelper.requireNonNull(completableSupplier, "completableSupplier"); @@ -267,6 +359,8 @@ public static Completable defer(final Callable<? extends CompletableSource> comp * Creates a Completable which calls the given error supplier for each subscriber * and emits its returned Throwable. * <p> + * <img width="640" height="462" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.error.f.png" alt=""> + * <p> * If the errorSupplier returns null, the child CompletableObservers will receive a * NullPointerException. * <dl> @@ -278,6 +372,7 @@ public static Completable defer(final Callable<? extends CompletableSource> comp * @throws NullPointerException if errorSupplier is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable error(final Callable<? extends Throwable> errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -286,6 +381,8 @@ public static Completable error(final Callable<? extends Throwable> errorSupplie /** * Creates a Completable instance that emits the given Throwable exception to subscribers. + * <p> + * <img width="640" height="462" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.error.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code error} does not operate by default on a particular {@link Scheduler}.</dd> @@ -295,25 +392,35 @@ public static Completable error(final Callable<? extends Throwable> errorSupplie * @throws NullPointerException if error is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable error(final Throwable error) { ObjectHelper.requireNonNull(error, "error is null"); return RxJavaPlugins.onAssembly(new CompletableError(error)); } - /** * Returns a Completable instance that runs the given Action for each subscriber and * emits either an unchecked exception or simply completes. + * <p> + * <img width="640" height="297" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromAction.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromAction} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Action} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * @param run the runnable to run for each subscriber * @return the new Completable instance * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromAction(final Action run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -323,14 +430,24 @@ public static Completable fromAction(final Action run) { /** * Returns a Completable which when subscribed, executes the callable function, ignores its * normal result and emits onError or onComplete only. + * <p> + * <img width="640" height="286" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromCallable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromCallable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * @param callable the callable instance to execute for each subscriber * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromCallable(final Callable<?> callable) { ObjectHelper.requireNonNull(callable, "callable is null"); @@ -340,7 +457,9 @@ public static Completable fromCallable(final Callable<?> callable) { /** * Returns a Completable instance that reacts to the termination of the given Future in a blocking fashion. * <p> - * Note that cancellation from any of the subscribers to this Completable will cancel the future. + * <img width="640" height="628" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromFuture.png" alt=""> + * <p> + * Note that if any of the observers to this Completable call dispose, this Completable will cancel the future. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromFuture} does not operate by default on a particular {@link Scheduler}.</dd> @@ -349,24 +468,60 @@ public static Completable fromCallable(final Callable<?> callable) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromFuture(final Future<?> future) { ObjectHelper.requireNonNull(future, "future is null"); return fromAction(Functions.futureAction(future)); } + /** + * Returns a Completable instance that when subscribed to, subscribes to the {@code Maybe} instance and + * emits a completion event if the maybe emits {@code onSuccess}/{@code onComplete} or forwards any + * {@code onError} events. + * <p> + * <img width="640" height="235" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromMaybe.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code fromMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.17 - beta + * @param <T> the value type of the {@link MaybeSource} element + * @param maybe the Maybe instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if single is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Completable fromMaybe(final MaybeSource<T> maybe) { + ObjectHelper.requireNonNull(maybe, "maybe is null"); + return RxJavaPlugins.onAssembly(new MaybeIgnoreElementCompletable<T>(maybe)); + } + /** * Returns a Completable instance that runs the given Runnable for each subscriber and * emits either its exception or simply completes. + * <p> + * <img width="640" height="297" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromRunnable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromRunnable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Runnable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * @param run the runnable to run for each subscriber * @return the new Completable instance * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable fromRunnable(final Runnable run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -376,6 +531,8 @@ public static Completable fromRunnable(final Runnable run) { /** * Returns a Completable instance that subscribes to the given Observable, ignores all values and * emits only the terminal event. + * <p> + * <img width="640" height="414" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromObservable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromObservable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -386,6 +543,7 @@ public static Completable fromRunnable(final Runnable run) { * @throws NullPointerException if flowable is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Completable fromObservable(final ObservableSource<T> observable) { ObjectHelper.requireNonNull(observable, "observable is null"); @@ -395,6 +553,19 @@ public static <T> Completable fromObservable(final ObservableSource<T> observabl /** * Returns a Completable instance that subscribes to the given publisher, ignores all values and * emits only the terminal event. + * <p> + * <img width="640" height="422" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromPublisher.png" alt=""> + * <p> + * The {@link Publisher} must follow the + * <a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams">Reactive Streams specification</a>. + * Violating the specification may result in undefined behavior. + * <p> + * If possible, use {@link #create(CompletableOnSubscribe)} to create a + * source-like {@code Completable} instead. + * <p> + * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer @@ -406,8 +577,10 @@ public static <T> Completable fromObservable(final ObservableSource<T> observabl * @param publisher the Publisher instance to subscribe to, not null * @return the new Completable instance * @throws NullPointerException if publisher is null + * @see #create(CompletableOnSubscribe) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Completable fromPublisher(final Publisher<T> publisher) { @@ -418,6 +591,8 @@ public static <T> Completable fromPublisher(final Publisher<T> publisher) { /** * Returns a Completable instance that when subscribed to, subscribes to the Single instance and * emits a completion event if the single emits onSuccess or forwards any onError events. + * <p> + * <img width="640" height="356" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.fromSingle.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromSingle} does not operate by default on a particular {@link Scheduler}.</dd> @@ -428,6 +603,7 @@ public static <T> Completable fromPublisher(final Publisher<T> publisher) { * @throws NullPointerException if single is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Completable fromSingle(final SingleSource<T> single) { ObjectHelper.requireNonNull(single, "single is null"); @@ -437,15 +613,32 @@ public static <T> Completable fromSingle(final SingleSource<T> single) { /** * Returns a Completable instance that subscribes to all sources at once and * completes only when all source Completables complete or one of them emits an error. + * <p> + * <img width="640" height="270" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(CompletableSource...)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param sources the iterable sequence of sources. * @return the new Completable instance * @throws NullPointerException if sources is null + * @see #mergeArrayDelayError(CompletableSource...) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeArray(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -461,15 +654,32 @@ public static Completable mergeArray(CompletableSource... sources) { /** * Returns a Completable instance that subscribes to all sources at once and * completes only when all source Completables complete or one of them emits an error. + * <p> + * <img width="640" height="311" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.merge.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param sources the iterable sequence of sources. * @return the new Completable instance * @throws NullPointerException if sources is null + * @see #mergeDelayError(Iterable) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable merge(Iterable<? extends CompletableSource> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -479,16 +689,32 @@ public static Completable merge(Iterable<? extends CompletableSource> sources) { /** * Returns a Completable instance that subscribes to all sources at once and * completes only when all source Completables complete or one of them emits an error. + * <p> + * <img width="640" height="336" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.merge.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer * and expects the other {@code Publisher} to honor it as well.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param sources the iterable sequence of sources. * @return the new Completable instance * @throws NullPointerException if sources is null + * @see #mergeDelayError(Publisher) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -500,18 +726,34 @@ public static Completable merge(Publisher<? extends CompletableSource> sources) /** * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and * completes only when all source Completables complete or one of them emits an error. + * <p> + * <img width="640" height="269" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.merge.pn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer * and expects the other {@code Publisher} to honor it as well.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param sources the iterable sequence of sources. * @param maxConcurrency the maximum number of concurrent subscriptions * @return the new Completable instance * @throws NullPointerException if sources is null * @throws IllegalArgumentException if maxConcurrency is less than 1 + * @see #mergeDelayError(Publisher, int) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -539,6 +781,7 @@ public static Completable merge(Publisher<? extends CompletableSource> sources, * @throws IllegalArgumentException if maxConcurrency is less than 1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) private static Completable merge0(Publisher<? extends CompletableSource> sources, int maxConcurrency, boolean delayErrors) { @@ -551,6 +794,8 @@ private static Completable merge0(Publisher<? extends CompletableSource> sources * Returns a CompletableConsumable that subscribes to all Completables in the source array and delays * any error emitted by either the sources observable or any of the inner Completables until all of * them terminate in a way or another. + * <p> + * <img width="640" height="430" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeArrayDelayError.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -560,6 +805,7 @@ private static Completable merge0(Publisher<? extends CompletableSource> sources * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeArrayDelayError(CompletableSource... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -570,6 +816,8 @@ public static Completable mergeArrayDelayError(CompletableSource... sources) { * Returns a Completable that subscribes to all Completables in the source sequence and delays * any error emitted by either the sources observable or any of the inner Completables until all of * them terminate in a way or another. + * <p> + * <img width="640" height="475" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeDelayError.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -579,17 +827,19 @@ public static Completable mergeArrayDelayError(CompletableSource... sources) { * @throws NullPointerException if sources is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable mergeDelayError(Iterable<? extends CompletableSource> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); return RxJavaPlugins.onAssembly(new CompletableMergeDelayErrorIterable(sources)); } - /** * Returns a Completable that subscribes to all Completables in the source sequence and delays * any error emitted by either the sources observable or any of the inner Completables until all of * them terminate in a way or another. + * <p> + * <img width="640" height="466" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeDelayError.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer @@ -613,6 +863,8 @@ public static Completable mergeDelayError(Publisher<? extends CompletableSource> * the source sequence and delays any error emitted by either the sources * observable or any of the inner Completables until all of * them terminate in a way or another. + * <p> + * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeDelayError.pn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Completable} honors the backpressure of the downstream consumer @@ -634,6 +886,8 @@ public static Completable mergeDelayError(Publisher<? extends CompletableSource> /** * Returns a Completable that never calls onError or onComplete. + * <p> + * <img width="640" height="512" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.never.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code never} does not operate by default on a particular {@link Scheduler}.</dd> @@ -648,6 +902,8 @@ public static Completable never() { /** * Returns a Completable instance that fires its onComplete event after the given delay elapsed. + * <p> + * <img width="640" height="413" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timer.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timer} does operate by default on the {@code computation} {@link Scheduler}.</dd> @@ -665,6 +921,8 @@ public static Completable timer(long delay, TimeUnit unit) { /** * Returns a Completable instance that fires its onComplete event after the given delay elapsed * by using the supplied scheduler. + * <p> + * <img width="640" height="413" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timer.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timer} operates on the {@link Scheduler} you specify.</dd> @@ -675,6 +933,7 @@ public static Completable timer(long delay, TimeUnit unit) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Completable timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -697,6 +956,8 @@ private static NullPointerException toNpe(Throwable ex) { * Returns a Completable instance which manages a resource along * with a custom Completable instance while the subscription is active. * <p> + * <img width="640" height="388" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.using.png" alt=""> + * <p> * This overload disposes eagerly before the terminal event is emitted. * <dl> * <dt><b>Scheduler:</b></dt> @@ -721,7 +982,9 @@ public static <R> Completable using(Callable<R> resourceSupplier, * with a custom Completable instance while the subscription is active and performs eager or lazy * resource disposition. * <p> - * If this overload performs a lazy cancellation after the terminal event is emitted. + * <img width="640" height="332" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.using.b.png" alt=""> + * <p> + * If this overload performs a lazy disposal after the terminal event is emitted. * Exceptions thrown at this time will be delivered to RxJavaPlugins only. * <dl> * <dt><b>Scheduler:</b></dt> @@ -737,6 +1000,7 @@ public static <R> Completable using(Callable<R> resourceSupplier, * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <R> Completable using( final Callable<R> resourceSupplier, @@ -753,6 +1017,8 @@ public static <R> Completable using( /** * Wraps the given CompletableSource into a Completable * if not already Completable. + * <p> + * <img width="640" height="354" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.wrap.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code wrap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -762,6 +1028,7 @@ public static <R> Completable using( * @throws NullPointerException if source is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Completable wrap(CompletableSource source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -774,6 +1041,8 @@ public static Completable wrap(CompletableSource source) { /** * Returns a Completable that emits the a terminated event of either this Completable * or the other Completable whichever fires first. + * <p> + * <img width="640" height="484" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.ambWith.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -784,6 +1053,7 @@ public static Completable wrap(CompletableSource source) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable ambWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -795,6 +1065,8 @@ public final Completable ambWith(CompletableSource other) { * will subscribe to the {@code next} ObservableSource. An error event from this Completable will be * propagated to the downstream subscriber and will result in skipping the subscription of the * Observable. + * <p> + * <img width="640" height="278" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.andThen.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code andThen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -805,10 +1077,11 @@ public final Completable ambWith(CompletableSource other) { * @throws NullPointerException if next is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Observable<T> andThen(ObservableSource<T> next) { ObjectHelper.requireNonNull(next, "next is null"); - return RxJavaPlugins.onAssembly(new ObservableDelaySubscriptionOther<T, Object>(next, toObservable())); + return RxJavaPlugins.onAssembly(new CompletableAndThenObservable<T>(this, next)); } /** @@ -816,6 +1089,8 @@ public final <T> Observable<T> andThen(ObservableSource<T> next) { * will subscribe to the {@code next} Flowable. An error event from this Completable will be * propagated to the downstream subscriber and will result in skipping the subscription of the * Publisher. + * <p> + * <img width="640" height="249" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.andThen.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer @@ -829,11 +1104,12 @@ public final <T> Observable<T> andThen(ObservableSource<T> next) { * @throws NullPointerException if next is null */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <T> Flowable<T> andThen(Publisher<T> next) { ObjectHelper.requireNonNull(next, "next is null"); - return RxJavaPlugins.onAssembly(new FlowableDelaySubscriptionOther<T, Object>(next, toFlowable())); + return RxJavaPlugins.onAssembly(new CompletableAndThenPublisher<T>(this, next)); } /** @@ -841,6 +1117,8 @@ public final <T> Flowable<T> andThen(Publisher<T> next) { * will subscribe to the {@code next} SingleSource. An error event from this Completable will be * propagated to the downstream subscriber and will result in skipping the subscription of the * Single. + * <p> + * <img width="640" height="437" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.andThen.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code andThen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -851,6 +1129,7 @@ public final <T> Flowable<T> andThen(Publisher<T> next) { * @return Single that composes this Completable and next */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Single<T> andThen(SingleSource<T> next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -862,6 +1141,8 @@ public final <T> Single<T> andThen(SingleSource<T> next) { * will subscribe to the {@code next} MaybeSource. An error event from this Completable will be * propagated to the downstream subscriber and will result in skipping the subscription of the * Maybe. + * <p> + * <img width="640" height="280" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.andThen.m.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code andThen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -872,6 +1153,7 @@ public final <T> Single<T> andThen(SingleSource<T> next) { * @return Maybe that composes this Completable and next */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Maybe<T> andThen(MaybeSource<T> next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -882,6 +1164,8 @@ public final <T> Maybe<T> andThen(MaybeSource<T> next) { * Returns a Completable that first runs this Completable * and then the other completable. * <p> + * <img width="640" height="437" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.andThen.c.png" alt=""> + * <p> * This is an alias for {@link #concatWith(CompletableSource)}. * <dl> * <dt><b>Scheduler:</b></dt> @@ -894,15 +1178,45 @@ public final <T> Maybe<T> andThen(MaybeSource<T> next) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Completable andThen(CompletableSource next) { - return concatWith(next); + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, next)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * <img width="640" height="751" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.as.png" alt=""> + * <p> + * This allows fluent conversion to any other type. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code as} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current Completable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> R as(@NonNull CompletableConverter<? extends R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); } /** * Subscribes to and awaits the termination of this Completable instance in a blocking manner and * rethrows any exception emitted. + * <p> + * <img width="640" height="432" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.blockingAwait.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingAwait} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted */ @@ -916,9 +1230,15 @@ public final void blockingAwait() { /** * Subscribes to and awaits the termination of this Completable instance in a blocking manner * with a specific timeout and rethrows any exception emitted within the timeout window. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.blockingAwait.t.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingAwait} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * @param timeout the timeout value * @param unit the timeout unit @@ -927,6 +1247,7 @@ public final void blockingAwait() { * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final boolean blockingAwait(long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -938,6 +1259,8 @@ public final boolean blockingAwait(long timeout, TimeUnit unit) { /** * Subscribes to this Completable instance and blocks until it terminates, then returns null or * the emitted exception if any. + * <p> + * <img width="640" height="435" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.blockingGet.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingGet} does not operate by default on a particular {@link Scheduler}.</dd> @@ -945,6 +1268,7 @@ public final boolean blockingAwait(long timeout, TimeUnit unit) { * @return the throwable if this terminated with an error, null otherwise * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted */ + @Nullable @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Throwable blockingGet() { @@ -956,6 +1280,8 @@ public final Throwable blockingGet() { /** * Subscribes to this Completable instance and blocks until it terminates or the specified timeout * elapses, then returns null for normal termination or the emitted exception if any. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.blockingGet.t.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingGet} does not operate by default on a particular {@link Scheduler}.</dd> @@ -966,6 +1292,7 @@ public final Throwable blockingGet() { * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted or * TimeoutException if the specified timeout elapsed before it */ + @Nullable @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Throwable blockingGet(long timeout, TimeUnit unit) { @@ -980,6 +1307,8 @@ public final Throwable blockingGet(long timeout, TimeUnit unit) { * subscribes to the result Completable, caches its terminal event * and relays/replays it to observers. * <p> + * <img width="640" height="375" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.cache.png" alt=""> + * <p> * Note that this operator doesn't allow disposing the connection * of the upstream source. * <dl> @@ -999,6 +1328,8 @@ public final Completable cache() { /** * Calls the given transformer function with this instance and returns the function's resulting * Completable. + * <p> + * <img width="640" height="625" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.compose.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code compose} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1015,6 +1346,8 @@ public final Completable compose(CompletableTransformer transformer) { /** * Concatenates this Completable with another Completable. + * <p> + * <img width="640" height="317" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.concatWith.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1022,16 +1355,23 @@ public final Completable compose(CompletableTransformer transformer) { * @param other the other Completable, not null * @return the new Completable which subscribes to this and then the other Completable * @throws NullPointerException if other is null + * @see #andThen(MaybeSource) + * @see #andThen(ObservableSource) + * @see #andThen(SingleSource) + * @see #andThen(Publisher) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable concatWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); - return concatArray(this, other); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, other)); } /** * Returns a Completable which delays the emission of the completion event by the given time. + * <p> + * <img width="640" height="343" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.delay.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delay} does operate by default on the {@code computation} {@link Scheduler}.</dd> @@ -1050,6 +1390,8 @@ public final Completable delay(long delay, TimeUnit unit) { /** * Returns a Completable which delays the emission of the completion event by the given time while * running on the specified scheduler. + * <p> + * <img width="640" height="313" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.delay.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delay} operates on the {@link Scheduler} you specify.</dd> @@ -1069,6 +1411,8 @@ public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { /** * Returns a Completable which delays the emission of the completion event, and optionally the error as well, by the given time while * running on the specified scheduler. + * <p> + * <img width="640" height="253" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.delay.sb.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delay} operates on the {@link Scheduler} you specify.</dd> @@ -1081,6 +1425,7 @@ public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { * @throws NullPointerException if unit or scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable delay(final long delay, final TimeUnit unit, final Scheduler scheduler, final boolean delayError) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1088,8 +1433,57 @@ public final Completable delay(final long delay, final TimeUnit unit, final Sche return RxJavaPlugins.onAssembly(new CompletableDelay(this, delay, unit, scheduler, delayError)); } + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time. + * <p> + * <img width="640" height="475" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.delaySubscription.t.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @return a Completable that delays the subscription to the source CompletableSource by the given amount + * @since 2.2.3 - experimental + * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time, + * both waiting and subscribing on a given Scheduler. + * <p> + * <img width="640" height="420" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.delaySubscription.ts.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> + * </dl> + * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @param scheduler the Scheduler on which the waiting and subscription will happen + * @return a Completable that delays the subscription to the source CompletableSource by a given + * amount, waiting and subscribing on the given Scheduler + * @since 2.2.3 - experimental + * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return Completable.timer(delay, unit, scheduler).andThen(this); + } + /** * Returns a Completable which calls the given onComplete callback if this Completable completes. + * <p> + * <img width="640" height="304" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnComplete.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1097,6 +1491,7 @@ public final Completable delay(final long delay, final TimeUnit unit, final Sche * @param onComplete the callback to call when this emits an onComplete event * @return the new Completable instance * @throws NullPointerException if onComplete is null + * @see #doFinally(Action) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -1109,6 +1504,8 @@ public final Completable doOnComplete(Action onComplete) { /** * Calls the shared {@code Action} if a CompletableObserver subscribed to the current * Completable disposes the common Disposable it received via onSubscribe. + * <p> + * <img width="640" height="589" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnDispose.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1127,6 +1524,8 @@ public final Completable doOnDispose(Action onDispose) { /** * Returns a Completable which calls the given onError callback if this Completable emits an error. + * <p> + * <img width="640" height="304" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnError.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1134,6 +1533,7 @@ public final Completable doOnDispose(Action onDispose) { * @param onError the error callback * @return the new Completable instance * @throws NullPointerException if onError is null + * @see #doFinally(Action) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -1146,6 +1546,8 @@ public final Completable doOnError(Consumer<? super Throwable> onError) { /** * Returns a Completable which calls the given onEvent callback with the (throwable) for an onError * or (null) for an onComplete signal from this Completable before delivering said signal to the downstream. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnEvent.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1155,6 +1557,7 @@ public final Completable doOnError(Consumer<? super Throwable> onError) { * @throws NullPointerException if onEvent is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable doOnEvent(final Consumer<? super Throwable> onEvent) { ObjectHelper.requireNonNull(onEvent, "onEvent is null"); @@ -1176,6 +1579,7 @@ public final Completable doOnEvent(final Consumer<? super Throwable> onEvent) { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) private Completable doOnLifecycle( final Consumer<? super Disposable> onSubscribe, @@ -1196,6 +1600,8 @@ private Completable doOnLifecycle( /** * Returns a Completable instance that calls the given onSubscribe callback with the disposable * that child subscribers receive on subscription. + * <p> + * <img width="640" height="304" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnSubscribe.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1214,13 +1620,16 @@ public final Completable doOnSubscribe(Consumer<? super Disposable> onSubscribe) /** * Returns a Completable instance that calls the given onTerminate callback just before this Completable - * completes normally or with an exception + * completes normally or with an exception. + * <p> + * <img width="640" height="304" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doOnTerminate.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param onTerminate the callback to call just before this Completable terminates * @return the new Completable instance + * @see #doFinally(Action) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -1232,13 +1641,16 @@ public final Completable doOnTerminate(final Action onTerminate) { /** * Returns a Completable instance that calls the given onTerminate callback after this Completable - * completes normally or with an exception + * completes normally or with an exception. + * <p> + * <img width="640" height="304" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doAfterTerminate.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param onAfterTerminate the callback to call after this Completable terminates * @return the new Completable instance + * @see #doFinally(Action) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -1254,20 +1666,25 @@ public final Completable doAfterTerminate(final Action onAfterTerminate) { /** * Calls the specified action after this Completable signals onError or onComplete or gets disposed by * the downstream. - * <p>In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action + * <p> + * <img width="640" height="331" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.doFinally.png" alt=""> + * <p> + * In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action * is executed once per subscription. - * <p>Note that the {@code onFinally} action is shared between subscriptions and as such + * <p> + * Note that the {@code onFinally} action is shared between subscriptions and as such * should be thread-safe. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doFinally} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * <p>History: 2.0.1 - experimental - * @param onFinally the action called when this Completable terminates or gets cancelled + * @param onFinally the action called when this Completable terminates or gets disposed * @return the new Completable instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -1275,26 +1692,169 @@ public final Completable doFinally(Action onFinally) { } /** - * <strong>Advanced use without safeguards:</strong> lifts a CompletableOperator - * transformation into the chain of Completables. + * <strong>This method requires advanced knowledge about building operators, please consider + * other standard composition methods first;</strong> + * Returns a {@code Completable} which, when subscribed to, invokes the {@link CompletableOperator#apply(CompletableObserver) apply(CompletableObserver)} method + * of the provided {@link CompletableOperator} for each individual downstream {@link Completable} and allows the + * insertion of a custom operator by accessing the downstream's {@link CompletableObserver} during this subscription phase + * and providing a new {@code CompletableObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + * <p> + * <img width="640" height="313" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.lift.png" alt=""> + * <p> + * Generally, such a new {@code CompletableObserver} will wrap the downstream's {@code CompletableObserver} and forwards the + * {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + * <p> + * Example: + * <pre><code> + * // Step 1: Create the consumer type that will be returned by the CompletableOperator.apply(): + * + * public final class CustomCompletableObserver implements CompletableObserver, Disposable { + * + * // The downstream's CompletableObserver that will receive the onXXX events + * final CompletableObserver downstream; + * + * // The connection to the upstream source that will call this class' onXXX methods + * Disposable upstream; + * + * // The constructor takes the downstream subscriber and usually any other parameters + * public CustomCompletableObserver(CompletableObserver downstream) { + * this.downstream = downstream; + * } + * + * // In the subscription phase, the upstream sends a Disposable to this class + * // and subsequently this class has to send a Disposable to the downstream. + * // Note that relaying the upstream's Disposable directly is not allowed in RxJava + * @Override + * public void onSubscribe(Disposable d) { + * if (upstream != null) { + * d.dispose(); + * } else { + * upstream = d; + * downstream.onSubscribe(this); + * } + * } + * + * // Some operators may handle the upstream's error while others + * // could just forward it to the downstream. + * @Override + * public void onError(Throwable throwable) { + * downstream.onError(throwable); + * } + * + * // When the upstream completes, usually the downstream should complete as well. + * // In completable, this could also mean doing some side-effects + * @Override + * public void onComplete() { + * System.out.println("Sequence completed"); + * downstream.onComplete(); + * } + * + * // Some operators may use their own resources which should be cleaned up if + * // the downstream disposes the flow before it completed. Operators without + * // resources can simply forward the dispose to the upstream. + * // In some cases, a disposed flag may be set by this method so that other parts + * // of this class may detect the dispose and stop sending events + * // to the downstream. + * @Override + * public void dispose() { + * upstream.dispose(); + * } + * + * // Some operators may simply forward the call to the upstream while others + * // can return the disposed flag set in dispose(). + * @Override + * public boolean isDisposed() { + * return upstream.isDisposed(); + * } + * } + * + * // Step 2: Create a class that implements the CompletableOperator interface and + * // returns the custom consumer type from above in its apply() method. + * // Such class may define additional parameters to be submitted to + * // the custom consumer type. + * + * final class CustomCompletableOperator implements CompletableOperator { + * @Override + * public CompletableObserver apply(CompletableObserver upstream) { + * return new CustomCompletableObserver(upstream); + * } + * } + * + * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it + * // or reusing an existing one. + * + * Completable.complete() + * .lift(new CustomCompletableOperator()) + * .test() + * .assertResult(); + * </code></pre> + * <p> + * Creating custom operators can be complicated and it is recommended one consults the + * <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + * <p> + * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Completable} + * class and creating a {@link CompletableTransformer} with it is recommended. + * <p> + * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code CompletableObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Completable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code CompletableObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code CompletableTransformer} and extending the {@code Completable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}.</dd> + * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link CompletableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.</dd> * </dl> - * @param onLift the lifting function that transforms the child subscriber with a parent subscriber. + * + * @param onLift the {@link CompletableOperator} that receives the downstream's {@code CompletableObserver} and should return + * a {@code CompletableObserver} with custom behavior to be used as the consumer for the current + * {@code Completable}. * @return the new Completable instance - * @throws NullPointerException if onLift is null + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> + * @see #compose(CompletableTransformer) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable lift(final CompletableOperator onLift) { ObjectHelper.requireNonNull(onLift, "onLift is null"); return RxJavaPlugins.onAssembly(new CompletableLift(this, onLift)); } + /** + * Maps the signal types of this Completable into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + * <p> + * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/materialize.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code materialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the intended target element type of the notification + * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <T> Single<Notification<T>> materialize() { + return RxJavaPlugins.onAssembly(new CompletableMaterialize<T>(this)); + } + /** * Returns a Completable which subscribes to this and the other Completable and completes * when both of them complete or one emits an error. + * <p> + * <img width="640" height="442" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.mergeWith.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1304,6 +1864,7 @@ public final Completable lift(final CompletableOperator onLift) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable mergeWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1312,6 +1873,8 @@ public final Completable mergeWith(CompletableSource other) { /** * Returns a Completable which emits the terminal events from the thread of the specified scheduler. + * <p> + * <img width="640" height="523" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.observeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code observeOn} operates on a {@link Scheduler} you specify.</dd> @@ -1321,6 +1884,7 @@ public final Completable mergeWith(CompletableSource other) { * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1330,6 +1894,8 @@ public final Completable observeOn(final Scheduler scheduler) { /** * Returns a Completable instance that if this Completable emits an error, it will emit an onComplete * and swallow the throwable. + * <p> + * <img width="640" height="585" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.onErrorComplete.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1345,6 +1911,8 @@ public final Completable onErrorComplete() { /** * Returns a Completable instance that if this Completable emits an error and the predicate returns * true, it will emit an onComplete and swallow the throwable. + * <p> + * <img width="640" height="283" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.onErrorComplete.f.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1354,6 +1922,7 @@ public final Completable onErrorComplete() { * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable onErrorComplete(final Predicate<? super Throwable> predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -1365,6 +1934,8 @@ public final Completable onErrorComplete(final Predicate<? super Throwable> pred * Returns a Completable instance that when encounters an error from this Completable, calls the * specified mapper function that returns another Completable instance for it and resumes the * execution with it. + * <p> + * <img width="640" height="426" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.onErrorResumeNext.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1374,6 +1945,7 @@ public final Completable onErrorComplete(final Predicate<? super Throwable> pred * @return the new Completable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable onErrorResumeNext(final Function<? super Throwable, ? extends CompletableSource> errorMapper) { ObjectHelper.requireNonNull(errorMapper, "errorMapper is null"); @@ -1381,7 +1953,29 @@ public final Completable onErrorResumeNext(final Function<? super Throwable, ? e } /** - * Returns a Completable that repeatedly subscribes to this Completable until cancelled. + * Nulls out references to the upstream producer and downstream CompletableObserver if + * the sequence is terminated or downstream calls dispose(). + * <p> + * <img width="640" height="326" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.onTerminateDetach.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.5 - experimental + * @return a Completable which nulls out references to the upstream producer and downstream CompletableObserver if + * the sequence is terminated or downstream calls dispose() + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable onTerminateDetach() { + return RxJavaPlugins.onAssembly(new CompletableDetach(this)); + } + + /** + * Returns a Completable that repeatedly subscribes to this Completable until disposed. + * <p> + * <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.repeat.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1396,6 +1990,8 @@ public final Completable repeat() { /** * Returns a Completable that subscribes repeatedly at most the given times to this Completable. + * <p> + * <img width="640" height="408" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.repeat.n.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1413,6 +2009,8 @@ public final Completable repeat(long times) { /** * Returns a Completable that repeatedly subscribes to this Completable so long as the given * stop supplier returns false. + * <p> + * <img width="640" height="381" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.repeatUntil.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1430,6 +2028,8 @@ public final Completable repeatUntil(BooleanSupplier stop) { /** * Returns a Completable instance that repeats when the Publisher returned by the handler * emits an item or completes when this Publisher emits a completed event. + * <p> + * <img width="640" height="586" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.repeatWhen.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1448,6 +2048,8 @@ public final Completable repeatWhen(Function<? super Flowable<Object>, ? extends /** * Returns a Completable that retries this Completable as long as it emits an onError event. + * <p> + * <img width="640" height="368" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retry.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1463,6 +2065,8 @@ public final Completable retry() { /** * Returns a Completable that retries this Completable in case of an error as long as the predicate * returns true. + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retry.ff.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1480,11 +2084,13 @@ public final Completable retry(BiPredicate<? super Integer, ? super Throwable> p /** * Returns a Completable that when this Completable emits an error, retries at most the given * number of times before giving up and emitting the last error. + * <p> + * <img width="640" height="451" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retry.n.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param times the number of times the returned Completable should retry this Completable + * @param times the number of times to resubscribe if the current Completable fails * @return the new Completable instance * @throws IllegalArgumentException if times is negative */ @@ -1494,9 +2100,35 @@ public final Completable retry(long times) { return fromPublisher(toFlowable().retry(times)); } + /** + * Returns a Completable that when this Completable emits an error, retries at most times + * or until the predicate returns false, whichever happens first and emitting the last error. + * <p> + * <img width="640" height="361" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retry.nf.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.8 - experimental + * @param times the number of times to resubscribe if the current Completable fails + * @param predicate the predicate that is called with the latest throwable and should return + * true to indicate the returned Completable should resubscribe to this Completable. + * @return the new Completable instance + * @throws NullPointerException if predicate is null + * @throws IllegalArgumentException if times is negative + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry(long times, Predicate<? super Throwable> predicate) { + return fromPublisher(toFlowable().retry(times, predicate)); + } + /** * Returns a Completable that when this Completable emits an error, calls the given predicate with * the latest exception to decide whether to resubscribe to this or not. + * <p> + * <img width="640" height="336" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retry.f.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1516,6 +2148,33 @@ public final Completable retry(Predicate<? super Throwable> predicate) { * Returns a Completable which given a Publisher and when this Completable emits an error, delivers * that error through a Flowable and the Publisher should signal a value indicating a retry in response * or a terminal event indicating a termination. + * <p> + * <img width="640" height="586" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.retryWhen.png" alt=""> + * <p> + * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + * <p> + * The following example demonstrates how to retry an asynchronous source with a delay: + * <pre><code> + * Completable.timer(1, TimeUnit.SECONDS) + * .doOnSubscribe(s -> System.out.println("subscribing")) + * .doOnComplete(() -> { throw new RuntimeException(); }) + * .retryWhen(errors -> { + * AtomicInteger counter = new AtomicInteger(); + * return errors + * .takeWhile(e -> counter.getAndIncrement() != 3) + * .flatMap(e -> { + * System.out.println("delay retry by " + counter.get() + " second(s)"); + * return Flowable.timer(counter.get(), TimeUnit.SECONDS); + * }); + * }) + * .blockingAwait(); + * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1534,6 +2193,8 @@ public final Completable retryWhen(Function<? super Flowable<Throwable>, ? exten /** * Returns a Completable which first runs the other Completable * then this completable if the other completed normally. + * <p> + * <img width="640" height="437" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.startWith.c.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code startWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1543,6 +2204,7 @@ public final Completable retryWhen(Function<? super Flowable<Throwable>, ? exten * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable startWith(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1552,6 +2214,8 @@ public final Completable startWith(CompletableSource other) { /** * Returns an Observable which first delivers the events * of the other Observable then runs this CompletableConsumable. + * <p> + * <img width="640" height="289" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.startWith.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code startWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1562,6 +2226,7 @@ public final Completable startWith(CompletableSource other) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Observable<T> startWith(Observable<T> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1570,6 +2235,8 @@ public final <T> Observable<T> startWith(Observable<T> other) { /** * Returns a Flowable which first delivers the events * of the other Publisher then runs this Completable. + * <p> + * <img width="640" height="250" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.startWith.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer @@ -1583,6 +2250,7 @@ public final <T> Observable<T> startWith(Observable<T> other) { * @throws NullPointerException if other is null */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <T> Flowable<T> startWith(Publisher<T> other) { @@ -1592,8 +2260,10 @@ public final <T> Flowable<T> startWith(Publisher<T> other) { /** * Hides the identity of this Completable and its Disposable. - * <p>Allows preventing certain identity-based - * optimizations (fusion). + * <p> + * <img width="640" height="432" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.hide.png" alt=""> + * <p> + * Allows preventing certain identity-based optimizations (fusion). * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code hide} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1609,30 +2279,34 @@ public final Completable hide() { } /** - * Subscribes to this CompletableConsumable and returns a Disposable which can be used to cancel + * Subscribes to this CompletableConsumable and returns a Disposable which can be used to dispose * the subscription. + * <p> + * <img width="640" height="352" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.subscribe.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @return the Disposable that allows cancelling the subscription + * @return the Disposable that allows disposing the subscription */ @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe() { - EmptyCompletableObserver s = new EmptyCompletableObserver(); - subscribe(s); - return s; + EmptyCompletableObserver observer = new EmptyCompletableObserver(); + subscribe(observer); + return observer; } @SchedulerSupport(SchedulerSupport.NONE) @Override - public final void subscribe(CompletableObserver s) { - ObjectHelper.requireNonNull(s, "s is null"); + public final void subscribe(CompletableObserver observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); try { - s = RxJavaPlugins.onSubscribe(this, s); + observer = RxJavaPlugins.onSubscribe(this, observer); - subscribeActual(s); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null CompletableObserver. Please check the handler provided to RxJavaPlugins.setOnCompletableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + subscribeActual(observer); } catch (NullPointerException ex) { // NOPMD throw ex; } catch (Throwable ex) { @@ -1643,25 +2317,30 @@ public final void subscribe(CompletableObserver s) { } /** - * Implement this to handle the incoming CompletableObserver and + * Implement this method to handle the incoming {@link CompletableObserver}s and * perform the business logic in your operator. - * @param s the CompletableObserver instance, never null + * <p>There is no need to call any of the plugin hooks on the current {@code Completable} instance or + * the {@code CompletableObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(CompletableObserver)} before this method gets called. + * @param observer the CompletableObserver instance, never null */ - protected abstract void subscribeActual(CompletableObserver s); + protected abstract void subscribeActual(CompletableObserver observer); /** * Subscribes a given CompletableObserver (subclass) to this Completable and returns the given * CompletableObserver as is. + * <p> + * <img width="640" height="349" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.subscribeWith.png" alt=""> * <p>Usage example: * <pre><code> * Completable source = Completable.complete().delay(1, TimeUnit.SECONDS); * CompositeDisposable composite = new CompositeDisposable(); * - * class ResourceCompletableObserver implements CompletableObserver, Disposable { + * DisposableCompletableObserver ds = new DisposableCompletableObserver() { * // ... - * } + * }; * - * composite.add(source.subscribeWith(new ResourceCompletableObserver())); + * composite.add(source.subscribeWith(ds)); * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> @@ -1682,30 +2361,35 @@ public final <E extends CompletableObserver> E subscribeWith(E observer) { /** * Subscribes to this Completable and calls back either the onError or onComplete functions. + * <p> + * <img width="640" height="352" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.subscribe.ff.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param onComplete the runnable that is called if the Completable completes normally * @param onError the consumer that is called if this Completable emits an error - * @return the Disposable that can be used for cancelling the subscription asynchronously + * @return the Disposable that can be used for disposing the subscription asynchronously * @throws NullPointerException if either callback is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Action onComplete, final Consumer<? super Throwable> onError) { ObjectHelper.requireNonNull(onError, "onError is null"); ObjectHelper.requireNonNull(onComplete, "onComplete is null"); - CallbackCompletableObserver s = new CallbackCompletableObserver(onError, onComplete); - subscribe(s); - return s; + CallbackCompletableObserver observer = new CallbackCompletableObserver(onError, onComplete); + subscribe(observer); + return observer; } /** * Subscribes to this Completable and calls the given Action when this Completable * completes normally. * <p> + * <img width="640" height="352" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.subscribe.f.png" alt=""> + * <p> * If the Completable emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -1714,21 +2398,24 @@ public final Disposable subscribe(final Action onComplete, final Consumer<? supe * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param onComplete the runnable called when this Completable completes normally - * @return the Disposable that allows cancelling the subscription + * @return the Disposable that allows disposing the subscription */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Action onComplete) { ObjectHelper.requireNonNull(onComplete, "onComplete is null"); - CallbackCompletableObserver s = new CallbackCompletableObserver(onComplete); - subscribe(s); - return s; + CallbackCompletableObserver observer = new CallbackCompletableObserver(onComplete); + subscribe(observer); + return observer; } /** * Returns a Completable which subscribes the child subscriber on the specified scheduler, making * sure the subscription side-effects happen on that specific thread of the scheduler. + * <p> + * <img width="640" height="686" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.subscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribeOn} operates on a {@link Scheduler} you specify.</dd> @@ -1738,6 +2425,7 @@ public final Disposable subscribe(final Action onComplete) { * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable subscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1745,9 +2433,38 @@ public final Completable subscribeOn(final Scheduler scheduler) { return RxJavaPlugins.onAssembly(new CompletableSubscribeOn(this, scheduler)); } + /** + * Terminates the downstream if this or the other {@code Completable} + * terminates (wins the termination race) while disposing the connection to the losing source. + * <p> + * <img width="640" height="468" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.takeuntil.c.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code takeUntil} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If both this and the other sources signal an error, only one of the errors + * is signaled to the downstream and the other error is signaled to the global + * error handler via {@link RxJavaPlugins#onError(Throwable)}.</dd> + * </dl> + * <p>History: 2.1.17 - experimental + * @param other the other completable source to observe for the terminal signals + * @return the new Completable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable takeUntil(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + + return RxJavaPlugins.onAssembly(new CompletableTakeUntilCompletable(this, other)); + } + /** * Returns a Completable that runs this Completable and emits a TimeoutException in case * this Completable doesn't complete within the given time. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timeout.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} signals the TimeoutException on the {@code computation} {@link Scheduler}.</dd> @@ -1766,6 +2483,8 @@ public final Completable timeout(long timeout, TimeUnit unit) { /** * Returns a Completable that runs this Completable and switches to the other Completable * in case this Completable doesn't complete within the given time. + * <p> + * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timeout.c.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} subscribes to the other CompletableSource on @@ -1778,6 +2497,7 @@ public final Completable timeout(long timeout, TimeUnit unit) { * @throws NullPointerException if unit or other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Completable timeout(long timeout, TimeUnit unit, CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1788,6 +2508,8 @@ public final Completable timeout(long timeout, TimeUnit unit, CompletableSource * Returns a Completable that runs this Completable and emits a TimeoutException in case * this Completable doesn't complete within the given time while "waiting" on the specified * Scheduler. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timeout.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} signals the TimeoutException on the {@link Scheduler} you specify.</dd> @@ -1808,6 +2530,8 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * Returns a Completable that runs this Completable and switches to the other Completable * in case this Completable doesn't complete within the given time while "waiting" on * the specified scheduler. + * <p> + * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.timeout.sc.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} subscribes to the other CompletableSource on @@ -1821,6 +2545,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * @throws NullPointerException if unit, scheduler or other is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1844,6 +2569,7 @@ public final Completable timeout(long timeout, TimeUnit unit, Scheduler schedule * @throws NullPointerException if unit or scheduler */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) private Completable timeout0(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1853,6 +2579,8 @@ private Completable timeout0(long timeout, TimeUnit unit, Scheduler scheduler, C /** * Allows fluent conversion to another type via a function callback. + * <p> + * <img width="640" height="751" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.to.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code to} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1876,6 +2604,8 @@ public final <U> U to(Function<? super Completable, U> converter) { /** * Returns a Flowable which when subscribed to subscribes to this Completable and * relays the terminal events to the subscriber. + * <p> + * <img width="640" height="585" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toFlowable.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -1899,14 +2629,15 @@ public final <T> Flowable<T> toFlowable() { /** * Converts this Completable into a {@link Maybe}. * <p> - * <img width="640" height="293" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toObservable.png" alt=""> + * <img width="640" height="585" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toMaybe.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMaybe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * * @param <T> the value type - * @return a {@link Maybe} that emits a single item T or an error. + * @return a {@link Maybe} that only calls {@code onComplete} or {@code onError}, based on which one is + * called by the source Completable. */ @CheckReturnValue @SuppressWarnings("unchecked") @@ -1921,6 +2652,8 @@ public final <T> Maybe<T> toMaybe() { /** * Returns an Observable which when subscribed to subscribes to this Completable and * relays the terminal events to the subscriber. + * <p> + * <img width="640" height="293" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toObservable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toObservable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1941,6 +2674,8 @@ public final <T> Observable<T> toObservable() { /** * Converts this Completable into a Single which when this Completable completes normally, * calls the given supplier and emits its returned value through onSuccess. + * <p> + * <img width="640" height="583" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toSingle.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSingle} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1951,6 +2686,7 @@ public final <T> Observable<T> toObservable() { * @throws NullPointerException if completionValueSupplier is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Single<T> toSingle(final Callable<? extends T> completionValueSupplier) { ObjectHelper.requireNonNull(completionValueSupplier, "completionValueSupplier is null"); @@ -1960,6 +2696,8 @@ public final <T> Single<T> toSingle(final Callable<? extends T> completionValueS /** * Converts this Completable into a Single which when this Completable completes normally, * emits the given value through onSuccess. + * <p> + * <img width="640" height="583" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toSingleDefault.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSingleDefault} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1970,6 +2708,7 @@ public final <T> Single<T> toSingle(final Callable<? extends T> completionValueS * @throws NullPointerException if completionValue is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <T> Single<T> toSingleDefault(final T completionValue) { ObjectHelper.requireNonNull(completionValue, "completionValue is null"); @@ -1977,17 +2716,20 @@ public final <T> Single<T> toSingleDefault(final T completionValue) { } /** - * Returns a Completable which makes sure when a subscriber cancels the subscription, the - * dispose is called on the specified scheduler + * Returns a Completable which makes sure when a subscriber disposes the subscription, the + * dispose is called on the specified scheduler. + * <p> + * <img width="640" height="716" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.unsubscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.</dd> * </dl> - * @param scheduler the target scheduler where to execute the cancellation + * @param scheduler the target scheduler where to execute the disposing * @return the new Completable instance * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Completable unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -2000,6 +2742,8 @@ public final Completable unsubscribeOn(final Scheduler scheduler) { /** * Creates a TestObserver and subscribes * it to this Completable. + * <p> + * <img width="640" height="458" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.test.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code test} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2010,15 +2754,17 @@ public final Completable unsubscribeOn(final Scheduler scheduler) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<Void> test() { - TestObserver<Void> ts = new TestObserver<Void>(); - subscribe(ts); - return ts; + TestObserver<Void> to = new TestObserver<Void>(); + subscribe(to); + return to; } /** * Creates a TestObserver optionally in cancelled state, then subscribes it to this Completable. * @param cancelled if true, the TestObserver will be cancelled before subscribing to this * Completable. + * <p> + * <img width="640" height="499" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.test.b.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code test} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2029,12 +2775,12 @@ public final TestObserver<Void> test() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<Void> test(boolean cancelled) { - TestObserver<Void> ts = new TestObserver<Void>(); + TestObserver<Void> to = new TestObserver<Void>(); if (cancelled) { - ts.cancel(); + to.cancel(); } - subscribe(ts); - return ts; + subscribe(to); + return to; } } diff --git a/src/main/java/io/reactivex/CompletableConverter.java b/src/main/java/io/reactivex/CompletableConverter.java new file mode 100644 index 0000000000..1bea8631c8 --- /dev/null +++ b/src/main/java/io/reactivex/CompletableConverter.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Completable#as} operator to turn a Completable into another + * value fluently. + * <p>History: 2.1.7 - experimental + * @param <R> the output type + * @since 2.2 + */ +public interface CompletableConverter<R> { + /** + * Applies a function to the upstream Completable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Completable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Completable upstream); +} diff --git a/src/main/java/io/reactivex/CompletableEmitter.java b/src/main/java/io/reactivex/CompletableEmitter.java index 7a9dfac549..ffbd9ba37b 100644 --- a/src/main/java/io/reactivex/CompletableEmitter.java +++ b/src/main/java/io/reactivex/CompletableEmitter.java @@ -21,9 +21,29 @@ * Abstraction over an RxJava {@link CompletableObserver} that allows associating * a resource with it. * <p> - * All methods are safe to call from multiple threads. + * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. * <p> - * Calling onComplete or onError multiple times has no effect. + * Calling {@link #onComplete()} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after {@code onComplete} will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * <p> + * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls + * {@link #onError(Throwable)}, {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + * <p> + * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + * <p> + * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. */ public interface CompletableEmitter { @@ -39,22 +59,25 @@ public interface CompletableEmitter { void onError(@NonNull Throwable t); /** - * Sets a Disposable on this emitter; any previous Disposable - * or Cancellation will be disposed/cancelled. + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param d the disposable, null is allowed */ void setDisposable(@Nullable Disposable d); /** - * Sets a Cancellable on this emitter; any previous Disposable - * or Cancellation will be disposed/cancelled. + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param c the cancellable resource, null is allowed */ void setCancellable(@Nullable Cancellable c); /** - * Returns true if the downstream disposed the sequence. - * @return true if the downstream disposed the sequence + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, + * {@link #onComplete} or a successful {@link #tryOnError(Throwable)}. + * <p>This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated */ boolean isDisposed(); @@ -65,11 +88,11 @@ public interface CompletableEmitter { * <p> * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called * if the error could not be delivered. + * <p>History: 2.1.1 - experimental * @param t the throwable error to signal if possible * @return true if successful, false if the downstream is not able to accept further * events - * @since 2.1.1 - experimental + * @since 2.2 */ - @Experimental boolean tryOnError(@NonNull Throwable t); } diff --git a/src/main/java/io/reactivex/CompletableObserver.java b/src/main/java/io/reactivex/CompletableObserver.java index 54695d9a41..eac7c9436b 100644 --- a/src/main/java/io/reactivex/CompletableObserver.java +++ b/src/main/java/io/reactivex/CompletableObserver.java @@ -17,7 +17,35 @@ import io.reactivex.disposables.Disposable; /** - * Represents the subscription API callbacks when subscribing to a Completable instance. + * Provides a mechanism for receiving push-based notification of a valueless completion or an error. + * <p> + * When a {@code CompletableObserver} is subscribed to a {@link CompletableSource} through the {@link CompletableSource#subscribe(CompletableObserver)} method, + * the {@code CompletableSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code CompletableSource} will call a {@code CompletableObserver}'s {@link #onError(Throwable)} + * or {@link #onComplete()} method exactly once as they are considered mutually exclusive <strong>terminal signals</strong>. + * <p> + * Calling the {@code CompletableObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + * <pre><code> onSubscribe (onError | onComplete)?</code></pre> + * <p> + * Subscribing a {@code CompletableObserver} to multiple {@code CompletableSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code CompletableObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + * <p> + * Calling {@link #onSubscribe(Disposable)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + * <p> + * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + * <ul> + * <li>If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + * </li> + * <li>If there is a fatal error (such as {@code VirtualMachineError}).</li> + * </ul> + * @since 2.0 */ public interface CompletableObserver { /** diff --git a/src/main/java/io/reactivex/CompletableOnSubscribe.java b/src/main/java/io/reactivex/CompletableOnSubscribe.java index f4dae79f5d..0610a9b3a6 100644 --- a/src/main/java/io/reactivex/CompletableOnSubscribe.java +++ b/src/main/java/io/reactivex/CompletableOnSubscribe.java @@ -23,9 +23,9 @@ public interface CompletableOnSubscribe { /** * Called for each CompletableObserver that subscribes. - * @param e the safe emitter instance, never null + * @param emitter the safe emitter instance, never null * @throws Exception on error */ - void subscribe(@NonNull CompletableEmitter e) throws Exception; + void subscribe(@NonNull CompletableEmitter emitter) throws Exception; } diff --git a/src/main/java/io/reactivex/CompletableSource.java b/src/main/java/io/reactivex/CompletableSource.java index 57019f3164..145b0404f7 100644 --- a/src/main/java/io/reactivex/CompletableSource.java +++ b/src/main/java/io/reactivex/CompletableSource.java @@ -24,8 +24,8 @@ public interface CompletableSource { /** * Subscribes the given CompletableObserver to this CompletableSource instance. - * @param cs the CompletableObserver, not null - * @throws NullPointerException if {@code cs} is null + * @param co the CompletableObserver, not null + * @throws NullPointerException if {@code co} is null */ - void subscribe(@NonNull CompletableObserver cs); + void subscribe(@NonNull CompletableObserver co); } diff --git a/src/main/java/io/reactivex/Emitter.java b/src/main/java/io/reactivex/Emitter.java index 2f60f90478..0d95e80dcf 100644 --- a/src/main/java/io/reactivex/Emitter.java +++ b/src/main/java/io/reactivex/Emitter.java @@ -17,6 +17,11 @@ /** * Base interface for emitting signals in a push-fashion in various generator-like source * operators (create, generate). + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently. Calling them from multiple threads is not supported and leads to an + * undefined behavior. * * @param <T> the value type emitted */ diff --git a/src/main/java/io/reactivex/Flowable.java b/src/main/java/io/reactivex/Flowable.java index 7e2416b515..41cde991a7 100644 --- a/src/main/java/io/reactivex/Flowable.java +++ b/src/main/java/io/reactivex/Flowable.java @@ -25,7 +25,8 @@ import io.reactivex.internal.functions.*; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.flowable.*; -import io.reactivex.internal.operators.observable.ObservableFromPublisher; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.observable.*; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.subscribers.*; import io.reactivex.internal.util.*; @@ -35,12 +36,12 @@ import io.reactivex.subscribers.*; /** - * The Flowable class that implements the Reactive-Streams Pattern and offers factory methods, - * intermediate operators and the ability to consume reactive dataflows. + * The Flowable class that implements the <a href="https://github.com/reactive-streams/reactive-streams-jvm">Reactive Streams</a> + * Pattern and offers factory methods, intermediate operators and the ability to consume reactive dataflows. * <p> - * Reactive-Streams operates with {@code Publisher}s which {@code Flowable} extends. Many operators + * Reactive Streams operates with {@link Publisher}s which {@code Flowable} extends. Many operators * therefore accept general {@code Publisher}s directly and allow direct interoperation with other - * Reactive-Streams implementations. + * Reactive Streams implementations. * <p> * The Flowable hosts the default buffer size of 128 elements for operators, accessible via {@link #bufferSize()}, * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have @@ -50,11 +51,103 @@ * <p> * <img width="640" height="317" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/legend.png" alt=""> * <p> + * The {@code Flowable} follows the protocol + * <pre><code> + * onSubscribe onNext* (onError | onComplete)? + * </code></pre> + * where the stream can be disposed through the {@link Subscription} instance provided to consumers through + * {@link Subscriber#onSubscribe(Subscription)}. + * Unlike the {@code Observable.subscribe()} of version 1.x, {@link #subscribe(Subscriber)} does not allow external cancellation + * of a subscription and the {@link Subscriber} instance is expected to expose such capability if needed. + * <p> + * Flowables support backpressure and require {@link Subscriber}s to signal demand via {@link Subscription#request(long)}. + * <p> + * Example: + * <pre><code> + * Disposable d = Flowable.just("Hello world!") + * .delay(1, TimeUnit.SECONDS) + * .subscribeWith(new DisposableSubscriber<String>() { + * @Override public void onStart() { + * System.out.println("Start!"); + * request(1); + * } + * @Override public void onNext(String t) { + * System.out.println(t); + * request(1); + * } + * @Override public void onError(Throwable t) { + * t.printStackTrace(); + * } + * @Override public void onComplete() { + * System.out.println("Done!"); + * } + * }); + * + * Thread.sleep(500); + * // the sequence can now be cancelled via dispose() + * d.dispose(); + * </code></pre> + * <p> + * The Reactive Streams specification is relatively strict when defining interactions between {@code Publisher}s and {@code Subscriber}s, so much so + * that there is a significant performance penalty due certain timing requirements and the need to prepare for invalid + * request amounts via {@link Subscription#request(long)}. + * Therefore, RxJava has introduced the {@link FlowableSubscriber} interface that indicates the consumer can be driven with relaxed rules. + * All RxJava operators are implemented with these relaxed rules in mind. + * If the subscribing {@code Subscriber} does not implement this interface, for example, due to it being from another Reactive Streams compliant + * library, the Flowable will automatically apply a compliance wrapper around it. + * <p> + * {@code Flowable} is an abstract class, but it is not advised to implement sources and custom operators by extending the class directly due + * to the large amounts of <a href="https://github.com/reactive-streams/reactive-streams-jvm#specification">Reactive Streams</a> + * rules to be followed to the letter. See <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">the wiki</a> for + * some guidance if such custom implementations are necessary. + * <p> + * The recommended way of creating custom {@code Flowable}s is by using the {@link #create(FlowableOnSubscribe, BackpressureStrategy)} factory method: + * <pre><code> + * Flowable<String> source = Flowable.create(new FlowableOnSubscribe<String>() { + * @Override + * public void subscribe(FlowableEmitter<String> emitter) throws Exception { + * + * // signal an item + * emitter.onNext("Hello"); + * + * // could be some blocking operation + * Thread.sleep(1000); + * + * // the consumer might have cancelled the flow + * if (emitter.isCancelled() { + * return; + * } + * + * emitter.onNext("World"); + * + * Thread.sleep(1000); + * + * // the end-of-sequence has to be signaled, otherwise the + * // consumers may never finish + * emitter.onComplete(); + * } + * }, BackpressureStrategy.BUFFER); + * + * System.out.println("Subscribe!"); + * + * source.subscribe(System.out::println); + * + * System.out.println("Done!"); + * </code></pre> + * <p> + * RxJava reactive sources, such as {@code Flowable}, are generally synchronous and sequential in nature. In the ReactiveX design, the location (thread) + * where operators run is <i>orthogonal</i> to when the operators can work with data. This means that asynchrony and parallelism + * has to be explicitly expressed via operators such as {@link #subscribeOn(Scheduler)}, {@link #observeOn(Scheduler)} and {@link #parallel()}. In general, + * operators featuring a {@link Scheduler} parameter are introducing this type of asynchrony into the flow. + * <p> * For more information see the <a href="http://reactivex.io/documentation/Publisher.html">ReactiveX * documentation</a>. * * @param <T> * the type of the items emitted by the Flowable + * @see Observable + * @see ParallelFlowable + * @see io.reactivex.subscribers.DisposableSubscriber */ public abstract class Flowable<T> implements Publisher<T> { /** The default buffer size. */ @@ -85,6 +178,7 @@ public abstract class Flowable<T> implements Publisher<T> { * @see <a href="http://reactivex.io/documentation/operators/amb.html">ReactiveX operators documentation: Amb</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> amb(Iterable<? extends Publisher<? extends T>> sources) { @@ -114,6 +208,7 @@ public static <T> Flowable<T> amb(Iterable<? extends Publisher<? extends T>> sou * @see <a href="http://reactivex.io/documentation/operators/amb.html">ReactiveX operators documentation: Amb</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> ambArray(Publisher<? extends T>... sources) { @@ -148,7 +243,7 @@ public static int bufferSize() { * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -192,7 +287,7 @@ public static <T, R> Flowable<R> combineLatest(Publisher<? extends T>[] sources, * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If there are no source Publishers provided, the resulting sequence completes immediately without emitting @@ -236,7 +331,7 @@ public static <T, R> Flowable<R> combineLatest(Function<? super Object[], ? exte * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -267,6 +362,7 @@ public static <T, R> Flowable<R> combineLatest(Function<? super Object[], ? exte */ @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) public static <T, R> Flowable<R> combineLatest(Publisher<? extends T>[] sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -288,7 +384,7 @@ public static <T, R> Flowable<R> combineLatest(Publisher<? extends T>[] sources, * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -333,7 +429,7 @@ public static <T, R> Flowable<R> combineLatest(Iterable<? extends Publisher<? ex * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting any items and @@ -364,6 +460,7 @@ public static <T, R> Flowable<R> combineLatest(Iterable<? extends Publisher<? ex */ @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) public static <T, R> Flowable<R> combineLatest(Iterable<? extends Publisher<? extends T>> sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -383,7 +480,7 @@ public static <T, R> Flowable<R> combineLatest(Iterable<? extends Publisher<? ex * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -429,7 +526,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Publisher<? extends T>[ * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If there are no source Publishers provided, the resulting sequence completes immediately without emitting @@ -475,7 +572,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Function<? super Object * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If there are no source Publishers provided, the resulting sequence completes immediately without emitting @@ -523,7 +620,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Function<? super Object * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -554,6 +651,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Function<? super Object */ @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) public static <T, R> Flowable<R> combineLatestDelayError(Publisher<? extends T>[] sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -577,7 +675,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Publisher<? extends T>[ * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -623,7 +721,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Iterable<? extends Publ * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting @@ -669,7 +767,7 @@ public static <T, R> Flowable<R> combineLatestDelayError(Iterable<? extends Publ * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -714,7 +812,7 @@ public static <T1, T2, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -745,6 +843,7 @@ public static <T1, T2, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, R> Flowable<R> combineLatest( @@ -763,7 +862,7 @@ public static <T1, T2, T3, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -797,6 +896,7 @@ public static <T1, T2, T3, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, R> Flowable<R> combineLatest( @@ -816,7 +916,7 @@ public static <T1, T2, T3, T4, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -853,6 +953,7 @@ public static <T1, T2, T3, T4, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, R> Flowable<R> combineLatest( @@ -874,7 +975,7 @@ public static <T1, T2, T3, T4, T5, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -914,6 +1015,7 @@ public static <T1, T2, T3, T4, T5, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> combineLatest( @@ -936,7 +1038,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -979,6 +1081,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> combineLatest( @@ -1003,7 +1106,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -1049,6 +1152,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> combineLatest( @@ -1074,7 +1178,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> combineLatest( * aggregation is defined by a specified function. * <p> * If any of the sources never produces an item but only terminates (normally or with an error), the - * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). * If that input source is also synchronous, other sources after it will not be subscribed to. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png" alt=""> @@ -1123,6 +1227,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Flowable<R> combineLatest( @@ -1164,6 +1269,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Flowable<R> combineLatest( */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat(Iterable<? extends Publisher<? extends T>> sources) { @@ -1181,7 +1287,7 @@ public static <T> Flowable<T> concat(Iterable<? extends Publisher<? extends T>> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} * sources are expected to honor backpressure as well. If the outer violates this, a - * {@code MissingBackpressureException} is signalled. If any of the inner {@code Publisher}s violates + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates * this, it <em>may</em> throw an {@code IllegalStateException} when an inner {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code concat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1210,7 +1316,7 @@ public static <T> Flowable<T> concat(Publisher<? extends Publisher<? extends T>> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} * sources are expected to honor backpressure as well. If the outer violates this, a - * {@code MissingBackpressureException} is signalled. If any of the inner {@code Publisher}s violates + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates * this, it <em>may</em> throw an {@code IllegalStateException} when an inner {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code concat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1259,6 +1365,7 @@ public static <T> Flowable<T> concat(Publisher<? extends Publisher<? extends T>> */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat(Publisher<? extends T> source1, Publisher<? extends T> source2) { @@ -1295,6 +1402,7 @@ public static <T> Flowable<T> concat(Publisher<? extends T> source1, Publisher<? */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat( @@ -1336,6 +1444,7 @@ public static <T> Flowable<T> concat( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat( @@ -1414,7 +1523,9 @@ public static <T> Flowable<T> concatArrayDelayError(Publisher<? extends T>... so } /** - * Concatenates a sequence of Publishers eagerly into a single stream of values. + * Concatenates an array of Publishers eagerly into a single stream of values. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Flowable.concatArrayEager.png" alt=""> * <p> * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source Publishers. The operator buffers the values emitted by these Publishers and then drains them @@ -1429,7 +1540,7 @@ public static <T> Flowable<T> concatArrayDelayError(Publisher<? extends T>... so * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <T> the value type - * @param sources a sequence of Publishers that need to be eagerly concatenated + * @param sources an array of Publishers that need to be eagerly concatenated * @return the new Publisher instance with the specified concatenation behavior * @since 2.0 */ @@ -1441,7 +1552,9 @@ public static <T> Flowable<T> concatArrayEager(Publisher<? extends T>... sources } /** - * Concatenates a sequence of Publishers eagerly into a single stream of values. + * Concatenates an array of Publishers eagerly into a single stream of values. + * <p> + * <img width="640" height="406" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Flowable.concatArrayEager.nn.png" alt=""> * <p> * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source Publishers. The operator buffers the values emitted by these Publishers and then drains them @@ -1456,14 +1569,15 @@ public static <T> Flowable<T> concatArrayEager(Publisher<? extends T>... sources * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <T> the value type - * @param sources a sequence of Publishers that need to be eagerly concatenated + * @param sources an array of Publishers that need to be eagerly concatenated * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE - * is interpreted as indication to subscribe to all sources at once + * is interpreted as an indication to subscribe to all sources at once * @param prefetch the number of elements to prefetch from each Publisher source * @return the new Publisher instance with the specified concatenation behavior * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1474,6 +1588,70 @@ public static <T> Flowable<T> concatArrayEager(int maxConcurrency, int prefetch, return RxJavaPlugins.onAssembly(new FlowableConcatMapEager(new FlowableFromArray(sources), Functions.identity(), maxConcurrency, prefetch, ErrorMode.IMMEDIATE)); } + /** + * Concatenates an array of {@link Publisher}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + * <p> + * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Flowable.concatArrayEagerDelayError.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code Publisher}s. The operator buffers the values emitted by these {@code Publisher}s + * and then drains them in order, each one after the previous one completes. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources an array of {@code Publisher}s that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static <T> Flowable<T> concatArrayEagerDelayError(Publisher<? extends T>... sources) { + return concatArrayEagerDelayError(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of {@link Publisher}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + * <p> + * <img width="640" height="359" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Flowable.concatArrayEagerDelayError.nn.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code Publisher}s. The operator buffers the values emitted by these {@code Publisher}s + * and then drains them in order, each one after the previous one completes. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources an array of {@code Publisher}s that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each {@code Publisher} source + * @return the new Flowable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static <T> Flowable<T> concatArrayEagerDelayError(int maxConcurrency, int prefetch, Publisher<? extends T>... sources) { + return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, true); + } + /** * Concatenates the Iterable sequence of Publishers into a single sequence by subscribing to each Publisher, * one after the other, one at a time and delays any errors till the all inner Publishers terminate. @@ -1482,7 +1660,7 @@ public static <T> Flowable<T> concatArrayEager(int maxConcurrency, int prefetch, * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} * sources are expected to honor backpressure as well. If the outer violates this, a - * {@code MissingBackpressureException} is signalled. If any of the inner {@code Publisher}s violates + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates * this, it <em>may</em> throw an {@code IllegalStateException} when an inner {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1494,6 +1672,7 @@ public static <T> Flowable<T> concatArrayEager(int maxConcurrency, int prefetch, */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concatDelayError(Iterable<? extends Publisher<? extends T>> sources) { @@ -1598,6 +1777,7 @@ public static <T> Flowable<T> concatEager(Publisher<? extends Publisher<? extend * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1657,6 +1837,7 @@ public static <T> Flowable<T> concatEager(Iterable<? extends Publisher<? extends * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -1714,6 +1895,7 @@ public static <T> Flowable<T> concatEager(Iterable<? extends Publisher<? extends * @see Cancellable */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> create(FlowableOnSubscribe<T> source, BackpressureStrategy mode) { @@ -1729,7 +1911,7 @@ public static <T> Flowable<T> create(FlowableOnSubscribe<T> source, Backpressure * <p> * <img width="640" height="340" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/defer.png" alt=""> * <p> - * The defer Subscriber allows you to defer or delay emitting items from a Publisher until such time as an + * The defer Subscriber allows you to defer or delay emitting items from a Publisher until such time as a * Subscriber subscribes to the Publisher. This allows a {@link Subscriber} to easily obtain updates or a * refreshed version of the sequence. * <dl> @@ -1750,6 +1932,7 @@ public static <T> Flowable<T> create(FlowableOnSubscribe<T> source, Backpressure * @see <a href="http://reactivex.io/documentation/operators/defer.html">ReactiveX operators documentation: Defer</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> defer(Callable<? extends Publisher<? extends T>> supplier) { @@ -1804,10 +1987,11 @@ public static <T> Flowable<T> empty() { * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> error(Callable<? extends Throwable> supplier) { - ObjectHelper.requireNonNull(supplier, "errorSupplier is null"); + ObjectHelper.requireNonNull(supplier, "supplier is null"); return RxJavaPlugins.onAssembly(new FlowableError<T>(supplier)); } @@ -1832,6 +2016,7 @@ public static <T> Flowable<T> error(Callable<? extends Throwable> supplier) { * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> error(final Throwable throwable) { @@ -1859,6 +2044,7 @@ public static <T> Flowable<T> error(final Throwable throwable) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> fromArray(T... items) { @@ -1885,6 +2071,13 @@ public static <T> Flowable<T> fromArray(T... items) { * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromCallable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Subscriber#onError(Throwable)}, + * except when the downstream has canceled this {@code Flowable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * * @param supplier @@ -1897,6 +2090,7 @@ public static <T> Flowable<T> fromArray(T... items) { * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> fromCallable(Callable<? extends T> supplier) { @@ -1910,12 +2104,12 @@ public static <T> Flowable<T> fromCallable(Callable<? extends T> supplier) { * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/from.Future.png" alt=""> * <p> * You can convert any object that supports the {@link Future} interface into a Publisher that emits the - * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} * method. * <p> * <em>Important note:</em> This Publisher is blocking on the thread it gets subscribed on; you cannot cancel it. * <p> - * Unlike 1.x, cancelling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. * <dl> * <dt><b>Backpressure:</b></dt> @@ -1933,6 +2127,7 @@ public static <T> Flowable<T> fromCallable(Callable<? extends T> supplier) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> fromFuture(Future<? extends T> future) { @@ -1946,10 +2141,10 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future) { * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/from.Future.png" alt=""> * <p> * You can convert any object that supports the {@link Future} interface into a Publisher that emits the - * return value of the {@link Future#get} method of that object, by passing the object into the {@code fromFuture} + * return value of the {@link Future#get} method of that object by passing the object into the {@code fromFuture} * method. * <p> - * Unlike 1.x, cancelling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. * <p> * <em>Important note:</em> This Publisher is blocking on the thread it gets subscribed on; you cannot cancel it. @@ -1973,6 +2168,7 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeout, TimeUnit unit) { @@ -1987,10 +2183,10 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeou * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/from.Future.png" alt=""> * <p> * You can convert any object that supports the {@link Future} interface into a Publisher that emits the - * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} * method. * <p> - * Unlike 1.x, cancelling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. * <p> * <em>Important note:</em> This Publisher is blocking; you cannot cancel it. @@ -2018,6 +2214,7 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeou */ @SuppressWarnings({ "unchecked", "cast" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeout, TimeUnit unit, Scheduler scheduler) { @@ -2031,16 +2228,16 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeou * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/from.Future.s.png" alt=""> * <p> * You can convert any object that supports the {@link Future} interface into a Publisher that emits the - * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} * method. * <p> - * Unlike 1.x, cancelling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param future @@ -2056,6 +2253,7 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future, long timeou */ @SuppressWarnings({ "cast", "unchecked" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public static <T> Flowable<T> fromFuture(Future<? extends T> future, Scheduler scheduler) { @@ -2084,6 +2282,7 @@ public static <T> Flowable<T> fromFuture(Future<? extends T> future, Scheduler s * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> fromIterable(Iterable<? extends T> source) { @@ -2092,8 +2291,19 @@ public static <T> Flowable<T> fromIterable(Iterable<? extends T> source) { } /** - * Converts an arbitrary Reactive-Streams Publisher into a Flowable if not already a + * Converts an arbitrary Reactive Streams Publisher into a Flowable if not already a * Flowable. + * <p> + * The {@link Publisher} must follow the + * <a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams">Reactive Streams specification</a>. + * Violating the specification may result in undefined behavior. + * <p> + * If possible, use {@link #create(FlowableOnSubscribe, BackpressureStrategy)} to create a + * source-like {@code Flowable} instead. + * <p> + * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator is a pass-through for backpressure and its behavior is determined by the @@ -2104,9 +2314,11 @@ public static <T> Flowable<T> fromIterable(Iterable<? extends T> source) { * @param <T> the value type of the flow * @param source the Publisher to convert * @return the new Flowable instance - * @throws NullPointerException if publisher is null + * @throws NullPointerException if the {@code source} {@code Publisher} is null + * @see #create(FlowableOnSubscribe, BackpressureStrategy) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -2114,7 +2326,7 @@ public static <T> Flowable<T> fromPublisher(final Publisher<? extends T> source) if (source instanceof Flowable) { return RxJavaPlugins.onAssembly((Flowable<T>)source); } - ObjectHelper.requireNonNull(source, "publisher is null"); + ObjectHelper.requireNonNull(source, "source is null"); return RxJavaPlugins.onAssembly(new FlowableFromPublisher<T>(source)); } @@ -2122,6 +2334,10 @@ public static <T> Flowable<T> fromPublisher(final Publisher<? extends T> source) /** * Returns a cold, synchronous, stateless and backpressure-aware generator of values. * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure.</dd> @@ -2132,11 +2348,12 @@ public static <T> Flowable<T> fromPublisher(final Publisher<? extends T> source) * @param <T> the generated value type * @param generator the Consumer called whenever a particular downstream Subscriber has * requested a value. The callback then should call {@code onNext}, {@code onError} or - * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> generate(final Consumer<Emitter<T>> generator) { @@ -2149,6 +2366,10 @@ public static <T> Flowable<T> generate(final Consumer<Emitter<T>> generator) { /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure.</dd> @@ -2161,11 +2382,12 @@ public static <T> Flowable<T> generate(final Consumer<Emitter<T>> generator) { * @param initialState the Callable to generate the initial state for each Subscriber * @param generator the Consumer called with the current state whenever a particular downstream Subscriber has * requested a value. The callback then should call {@code onNext}, {@code onError} or - * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiConsumer<S, Emitter<T>> generator) { @@ -2177,6 +2399,10 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure.</dd> @@ -2189,13 +2415,14 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiCons * @param initialState the Callable to generate the initial state for each Subscriber * @param generator the Consumer called with the current state whenever a particular downstream Subscriber has * requested a value. The callback then should call {@code onNext}, {@code onError} or - * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets canceled * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiConsumer<S, Emitter<T>> generator, @@ -2207,6 +2434,10 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure.</dd> @@ -2220,7 +2451,7 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, final BiCons * @param generator the Function called with the current state whenever a particular downstream Subscriber has * requested a value. The callback then should call {@code onNext}, {@code onError} or * {@code onComplete} to signal a value or a terminal event and should return a (new) state for - * the next invocation. Signalling multiple {@code onNext} + * the next invocation. Signaling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @return the new Flowable instance */ @@ -2234,6 +2465,10 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, BiFunction<S /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure.</dd> @@ -2247,13 +2482,14 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, BiFunction<S * @param generator the Function called with the current state whenever a particular downstream Subscriber has * requested a value. The callback then should call {@code onNext}, {@code onError} or * {@code onComplete} to signal a value or a terminal event and should return a (new) state for - * the next invocation. Signalling multiple {@code onNext} + * the next invocation. Signaling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets canceled * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Flowable<T> generate(Callable<S> initialState, BiFunction<S, Emitter<T>, S> generator, Consumer<? super S> disposeState) { @@ -2264,7 +2500,7 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, BiFunction<S } /** - * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever increasing numbers + * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever-increasing numbers * after each {@code period} of time thereafter. * <p> * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timer.p.png" alt=""> @@ -2283,7 +2519,7 @@ public static <T, S> Flowable<T> generate(Callable<S> initialState, BiFunction<S * the period of time between emissions of the subsequent numbers * @param unit * the time unit for both {@code initialDelay} and {@code period} - * @return a Flowable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * @return a Flowable that emits a 0L after the {@code initialDelay} and ever-increasing numbers after * each {@code period} of time thereafter * @see <a href="http://reactivex.io/documentation/operators/interval.html">ReactiveX operators documentation: Interval</a> * @since 1.0.12 @@ -2296,7 +2532,7 @@ public static Flowable<Long> interval(long initialDelay, long period, TimeUnit u } /** - * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever increasing numbers + * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever-increasing numbers * after each {@code period} of time thereafter, on a specified {@link Scheduler}. * <p> * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timer.ps.png" alt=""> @@ -2306,7 +2542,7 @@ public static Flowable<Long> interval(long initialDelay, long period, TimeUnit u * may lead to {@code MissingBackpressureException} at some point in the chain. * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param initialDelay @@ -2317,12 +2553,13 @@ public static Flowable<Long> interval(long initialDelay, long period, TimeUnit u * the time unit for both {@code initialDelay} and {@code period} * @param scheduler * the Scheduler on which the waiting happens and items are emitted - * @return a Flowable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * @return a Flowable that emits a 0L after the {@code initialDelay} and ever-increasing numbers after * each {@code period} of time thereafter, while running on the given Scheduler * @see <a href="http://reactivex.io/documentation/operators/interval.html">ReactiveX operators documentation: Interval</a> * @since 1.0.12 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable<Long> interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { @@ -2368,7 +2605,7 @@ public static Flowable<Long> interval(long period, TimeUnit unit) { * may lead to {@code MissingBackpressureException} at some point in the chain. * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param period @@ -2399,7 +2636,7 @@ public static Flowable<Long> interval(long period, TimeUnit unit, Scheduler sche * </dl> * @param start that start value of the range * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. - * @param initialDelay the initial delay before signalling the first value (the start) + * @param initialDelay the initial delay before signaling the first value (the start) * @param period the period between subsequent values * @param unit the unit of measure of the initialDelay and period amounts * @return the new Flowable instance @@ -2423,13 +2660,14 @@ public static Flowable<Long> intervalRange(long start, long count, long initialD * </dl> * @param start that start value of the range * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. - * @param initialDelay the initial delay before signalling the first value (the start) + * @param initialDelay the initial delay before signaling the first value (the start) * @param period the period between subsequent values * @param unit the unit of measure of the initialDelay and period amounts * @param scheduler the target scheduler where the values and terminal signals will be emitted * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable<Long> intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { @@ -2451,17 +2689,17 @@ public static Flowable<Long> intervalRange(long start, long count, long initialD } /** - * Returns a Flowable that emits a single item and then completes. + * Returns a Flowable that signals the given (constant reference) item and then completes. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.png" alt=""> * <p> - * To convert any object into a Publisher that emits that object, pass that object into the {@code just} - * method. + * Note that the item is taken and re-emitted as is and not computed by any means by {@code just}. Use {@link #fromCallable(Callable)} + * to generate a single item on demand (when {@code Subscriber}s subscribe to it). + * <p> + * See the multi-parameter overloads of {@code just} to emit more than one (constant reference) items one after the other. + * Use {@link #fromArray(Object...)} to emit an arbitrary number of items that are known upfront. * <p> - * This is similar to the {@link #fromArray(java.lang.Object[])} method, except that {@code from} will convert - * an {@link Iterable} object into a Publisher that emits each of the items in the Iterable, one at a - * time, while the {@code just} method converts an Iterable into a Publisher that emits the entire - * Iterable as a single item. + * To emit the items of an {@link Iterable} sequence (such as a {@link java.util.List}), use {@link #fromIterable(Iterable)}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream.</dd> @@ -2475,8 +2713,13 @@ public static Flowable<Long> intervalRange(long start, long count, long initialD * the type of that item * @return a Flowable that emits {@code value} as a single item and then completes * @see <a href="http://reactivex.io/documentation/operators/just.html">ReactiveX operators documentation: Just</a> + * @see #just(Object, Object) + * @see #fromCallable(Callable) + * @see #fromArray(Object...) + * @see #fromIterable(Iterable) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item) { @@ -2506,11 +2749,12 @@ public static <T> Flowable<T> just(T item) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); return fromArray(item1, item2); } @@ -2539,12 +2783,13 @@ public static <T> Flowable<T> just(T item1, T item2) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); return fromArray(item1, item2, item3); } @@ -2575,13 +2820,14 @@ public static <T> Flowable<T> just(T item1, T item2, T item3) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); return fromArray(item1, item2, item3, item4); } @@ -2614,14 +2860,15 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); return fromArray(item1, item2, item3, item4, item5); } @@ -2656,15 +2903,16 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5) */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, T item6) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); return fromArray(item1, item2, item3, item4, item5, item6); } @@ -2701,16 +2949,17 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7); } @@ -2749,17 +2998,18 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); } @@ -2800,18 +3050,19 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); } @@ -2854,19 +3105,20 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); - ObjectHelper.requireNonNull(item10, "The tenth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } @@ -2885,6 +3137,19 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2899,6 +3164,7 @@ public static <T> Flowable<T> just(T item1, T item2, T item3, T item4, T item5, * @throws IllegalArgumentException * if {@code maxConcurrency} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable, int, int) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2922,6 +3188,19 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(int, int, Publisher[])} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2936,6 +3215,7 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * @throws IllegalArgumentException * if {@code maxConcurrency} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeArrayDelayError(int, int, Publisher...) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2958,6 +3238,19 @@ public static <T> Flowable<T> mergeArray(int maxConcurrency, int bufferSize, Pub * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2966,6 +3259,7 @@ public static <T> Flowable<T> mergeArray(int maxConcurrency, int bufferSize, Pub * @return a Flowable that emits items that are the result of flattening the items emitted by the * Publishers in the Iterable * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2989,6 +3283,19 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3001,6 +3308,7 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * @throws IllegalArgumentException * if {@code maxConcurrency} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable, int) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -3025,6 +3333,19 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3033,6 +3354,7 @@ public static <T> Flowable<T> merge(Iterable<? extends Publisher<? extends T>> s * @return a Flowable that emits items that are the result of flattening the Publishers emitted by the * {@code source} Publisher * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Publisher) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -3056,6 +3378,19 @@ public static <T> Flowable<T> merge(Publisher<? extends Publisher<? extends T>> * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3068,6 +3403,7 @@ public static <T> Flowable<T> merge(Publisher<? extends Publisher<? extends T>> * @throws IllegalArgumentException * if {@code maxConcurrency} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Publisher, int) * @since 1.1.0 */ @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -3091,6 +3427,19 @@ public static <T> Flowable<T> merge(Publisher<? extends Publisher<? extends T>> * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(Publisher...)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3098,6 +3447,7 @@ public static <T> Flowable<T> merge(Publisher<? extends Publisher<? extends T>> * the array of Publishers * @return a Flowable that emits all of the items emitted by the Publishers in the Array * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeArrayDelayError(Publisher...) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -3120,6 +3470,19 @@ public static <T> Flowable<T> mergeArray(Publisher<? extends T>... sources) { * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3129,9 +3492,11 @@ public static <T> Flowable<T> mergeArray(Publisher<? extends T>... sources) { * a Publisher to be merged * @return a Flowable that emits all of the items emitted by the source Publishers * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Publisher, Publisher) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? extends T> source2) { @@ -3153,6 +3518,19 @@ public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3164,9 +3542,11 @@ public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? * a Publisher to be merged * @return a Flowable that emits all of the items emitted by the source Publishers * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Publisher, Publisher, Publisher) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? extends T> source2, Publisher<? extends T> source3) { @@ -3189,6 +3569,19 @@ public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? * backpressure; if violated, the operator <em>may</em> signal {@code MissingBackpressureException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher, Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -3202,9 +3595,11 @@ public static <T> Flowable<T> merge(Publisher<? extends T> source1, Publisher<? * a Publisher to be merged * @return a Flowable that emits all of the items emitted by the source Publishers * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Publisher, Publisher, Publisher, Publisher) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> merge( @@ -3253,7 +3648,6 @@ public static <T> Flowable<T> mergeDelayError(Iterable<? extends Publisher<? ext return fromIterable(sources).flatMap((Function)Functions.identity(), true); } - /** * Flattens an Iterable of Publishers into one Publisher, in a way that allows a Subscriber to receive all * successfully emitted items from each of the source Publishers without being interrupted by an error @@ -3515,6 +3909,7 @@ public static <T> Flowable<T> mergeArrayDelayError(Publisher<? extends T>... sou */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError(Publisher<? extends T> source1, Publisher<? extends T> source2) { @@ -3557,6 +3952,7 @@ public static <T> Flowable<T> mergeDelayError(Publisher<? extends T> source1, Pu */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError(Publisher<? extends T> source1, Publisher<? extends T> source2, Publisher<? extends T> source3) { @@ -3566,7 +3962,6 @@ public static <T> Flowable<T> mergeDelayError(Publisher<? extends T> source1, Pu return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), true, 3); } - /** * Flattens four Publishers into one Publisher, in a way that allows a Subscriber to receive all * successfully emitted items from all of the source Publishers without being interrupted by an error @@ -3603,6 +3998,7 @@ public static <T> Flowable<T> mergeDelayError(Publisher<? extends T> source1, Pu */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError( @@ -3818,6 +4214,7 @@ public static <T> Single<Boolean> sequenceEqual(Publisher<? extends T> source1, * @see <a href="http://reactivex.io/documentation/operators/sequenceequal.html">ReactiveX operators documentation: SequenceEqual</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<Boolean> sequenceEqual(Publisher<? extends T> source1, Publisher<? extends T> source2, @@ -3872,7 +4269,7 @@ public static <T> Single<Boolean> sequenceEqual(Publisher<? extends T> source1, * from the earlier-emitted Publisher and begins emitting items from the new one. * <p> * The resulting Publisher completes if both the outer Publisher and the last inner Publisher, if any, complete. - * If the outer Publisher signals an onError, the inner Publisher is cancelled and the error delivered in-sequence. + * If the outer Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an @@ -3912,7 +4309,7 @@ public static <T> Flowable<T> switchOnNext(Publisher<? extends Publisher<? exten * from the earlier-emitted Publisher and begins emitting items from the new one. * <p> * The resulting Publisher completes if both the outer Publisher and the last inner Publisher, if any, complete. - * If the outer Publisher signals an onError, the inner Publisher is cancelled and the error delivered in-sequence. + * If the outer Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an @@ -3951,7 +4348,7 @@ public static <T> Flowable<T> switchOnNext(Publisher<? extends Publisher<? exten * <p> * The resulting Publisher completes if both the main Publisher and the last inner Publisher, if any, complete. * If the main Publisher signals an onError, the termination of the last inner Publisher will emit that error as is - * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signalled. + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an @@ -3990,7 +4387,7 @@ public static <T> Flowable<T> switchOnNextDelayError(Publisher<? extends Publish * <p> * The resulting Publisher completes if both the main Publisher and the last inner Publisher, if any, complete. * If the main Publisher signals an onError, the termination of the last inner Publisher will emit that error as is - * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signalled. + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an @@ -4054,7 +4451,7 @@ public static Flowable<Long> timer(long delay, TimeUnit unit) { * <dd>This operator does not support backpressure as it uses time. If the downstream needs a slower rate * it should slow the timer or use something like {@link #onBackpressureDrop}.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -4068,6 +4465,7 @@ public static Flowable<Long> timer(long delay, TimeUnit unit) { * @see <a href="http://reactivex.io/documentation/operators/timer.html">ReactiveX operators documentation: Timer</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public static Flowable<Long> timer(long delay, TimeUnit unit, Scheduler scheduler) { @@ -4079,7 +4477,7 @@ public static Flowable<Long> timer(long delay, TimeUnit unit, Scheduler schedule /** * Create a Flowable by wrapping a Publisher <em>which has to be implemented according - * to the Reactive-Streams specification by handling backpressure and + * to the Reactive Streams specification by handling backpressure and * cancellation correctly; no safeguards are provided by the Flowable itself</em>. * <dl> * <dt><b>Backpressure:</b></dt> @@ -4096,6 +4494,7 @@ public static Flowable<Long> timer(long delay, TimeUnit unit, Scheduler schedule * instead. */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.NONE) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> unsafeCreate(Publisher<T> onSubscribe) { @@ -4140,7 +4539,7 @@ public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, /** * Constructs a Publisher that creates a dependent resource object which is disposed of just before * termination if you have set {@code disposeEagerly} to {@code true} and cancellation does not occur - * before termination. Otherwise resource disposal will occur on cancellation. Eager disposal is + * before termination. Otherwise, resource disposal will occur on cancellation. Eager disposal is * particularly appropriate for a synchronous Publisher that reuses resources. {@code disposeAction} will * only be called once per subscription. * <p> @@ -4169,6 +4568,7 @@ public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, @@ -4176,7 +4576,7 @@ public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, Consumer<? super D> resourceDisposer, boolean eager) { ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); ObjectHelper.requireNonNull(sourceSupplier, "sourceSupplier is null"); - ObjectHelper.requireNonNull(resourceDisposer, "disposer is null"); + ObjectHelper.requireNonNull(resourceDisposer, "resourceDisposer is null"); return RxJavaPlugins.onAssembly(new FlowableUsing<T, D>(resourceSupplier, sourceSupplier, resourceDisposer, eager)); } @@ -4192,8 +4592,8 @@ public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, * The resulting {@code Publisher<R>} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4225,6 +4625,7 @@ public static <T, D> Flowable<T> using(Callable<? extends D> resourceSupplier, * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Flowable<R> zip(Iterable<? extends Publisher<? extends T>> sources, Function<? super Object[], ? extends R> zipper) { @@ -4245,7 +4646,7 @@ public static <T, R> Flowable<R> zip(Iterable<? extends Publisher<? extends T>> * The resulting {@code Publisher<R>} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if + * The operator subscribes to its sources in the order they are specified and completes eagerly if * one of the sources is shorter than the rest while cancel the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if @@ -4279,6 +4680,7 @@ public static <T, R> Flowable<R> zip(Iterable<? extends Publisher<? extends T>> */ @SuppressWarnings({ "rawtypes", "unchecked", "cast" }) @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Flowable<R> zip(Publisher<? extends Publisher<? extends T>> sources, @@ -4302,8 +4704,8 @@ public static <T, R> Flowable<R> zip(Publisher<? extends Publisher<? extends T>> * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4337,6 +4739,7 @@ public static <T, R> Flowable<R> zip(Publisher<? extends Publisher<? extends T>> */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, R> Flowable<R> zip( @@ -4362,8 +4765,8 @@ public static <T1, T2, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4398,6 +4801,7 @@ public static <T1, T2, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, R> Flowable<R> zip( @@ -4408,7 +4812,6 @@ public static <T1, T2, R> Flowable<R> zip( return zipArray(Functions.toFunction(zipper), delayError, bufferSize(), source1, source2); } - /** * Returns a Flowable that emits the results of a specified combiner function applied to combinations of * two items emitted, in sequence, by two other Publishers. @@ -4424,8 +4827,8 @@ public static <T1, T2, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4461,6 +4864,7 @@ public static <T1, T2, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, R> Flowable<R> zip( @@ -4487,8 +4891,8 @@ public static <T1, T2, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4525,6 +4929,7 @@ public static <T1, T2, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, R> Flowable<R> zip( @@ -4552,8 +4957,8 @@ public static <T1, T2, T3, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4593,6 +4998,7 @@ public static <T1, T2, T3, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, R> Flowable<R> zip( @@ -4622,8 +5028,8 @@ public static <T1, T2, T3, T4, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4666,6 +5072,7 @@ public static <T1, T2, T3, T4, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, R> Flowable<R> zip( @@ -4695,8 +5102,8 @@ public static <T1, T2, T3, T4, T5, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4742,6 +5149,7 @@ public static <T1, T2, T3, T4, T5, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> zip( @@ -4772,8 +5180,8 @@ public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4822,6 +5230,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> zip( @@ -4854,8 +5263,8 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4907,6 +5316,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> zip( @@ -4940,8 +5350,8 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> zip( * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest * items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -4996,6 +5406,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Flowable<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Flowable<R> zip( @@ -5028,8 +5439,8 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Flowable<R> zip( * The resulting {@code Publisher<R>} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -5059,13 +5470,14 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Flowable<R> zip( * a function that, when applied to an item emitted by each of the source Publishers, results in * an item that will be emitted by the resulting Publisher * @param delayError - * delay errors signalled by any of the source Publisher until all Publishers terminate + * delay errors signaled by any of the source Publisher until all Publishers terminate * @param bufferSize * the number of elements to prefetch from each source Publisher * @return a Flowable that emits the zipped results * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Flowable<R> zipArray(Function<? super Object[], ? extends R> zipper, @@ -5090,8 +5502,8 @@ public static <T, R> Flowable<R> zipArray(Function<? super Object[], ? extends R * The resulting {@code Publisher<R>} returned from {@code zip} will invoke {@code onNext} as many times as * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -5119,7 +5531,7 @@ public static <T, R> Flowable<R> zipArray(Function<? super Object[], ? extends R * a function that, when applied to an item emitted by each of the source Publishers, results in * an item that will be emitted by the resulting Publisher * @param delayError - * delay errors signalled by any of the source Publisher until all Publishers terminate + * delay errors signaled by any of the source Publisher until all Publishers terminate * @param bufferSize * the number of elements to prefetch from each source Publisher * @param <T> the common source value type @@ -5128,6 +5540,7 @@ public static <T, R> Flowable<R> zipArray(Function<? super Object[], ? extends R * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Flowable<R> zipIterable(Iterable<? extends Publisher<? extends T>> sources, @@ -5163,6 +5576,7 @@ public static <T, R> Flowable<R> zipIterable(Iterable<? extends Publisher<? exte * @see <a href="http://reactivex.io/documentation/operators/all.html">ReactiveX operators documentation: All</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<Boolean> all(Predicate<? super T> predicate) { @@ -5192,6 +5606,7 @@ public final Single<Boolean> all(Predicate<? super T> predicate) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> ambWith(Publisher<? extends T> other) { @@ -5206,7 +5621,7 @@ public final Flowable<T> ambWith(Publisher<? extends T> other) { * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/exists.png" alt=""> * <p> - * In Rx.Net this is the {@code any} Subscriber but we renamed it in RxJava to better match Java naming + * In Rx.Net this is the {@code any} operator but we renamed it in RxJava to better match Java naming * idioms. * <dl> * <dt><b>Backpressure:</b></dt> @@ -5223,6 +5638,7 @@ public final Flowable<T> ambWith(Publisher<? extends T> other) { * @see <a href="http://reactivex.io/documentation/operators/contains.html">ReactiveX operators documentation: Contains</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<Boolean> any(Predicate<? super T> predicate) { @@ -5230,6 +5646,30 @@ public final Single<Boolean> any(Predicate<? super T> predicate) { return RxJavaPlugins.onAssembly(new FlowableAnySingle<T>(this, predicate)); } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * This allows fluent conversion to any other type. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The backpressure behavior depends on what happens in the {@code converter} function.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code as} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current Flowable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> R as(@NonNull FlowableConverter<T, ? extends R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + /** * Returns the first item emitted by this {@code Flowable}, or throws * {@code NoSuchElementException} if it emits no items. @@ -5239,6 +5679,10 @@ public final Single<Boolean> any(Predicate<? super T> predicate) { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @return the first item emitted by this {@code Flowable} @@ -5268,6 +5712,10 @@ public final T blockingFirst() { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param defaultItem @@ -5287,26 +5735,28 @@ public final T blockingFirst(T defaultItem) { } /** - * Invokes a method on each item emitted by this {@code Flowable} and blocks until the Flowable - * completes. - * <p> - * <em>Note:</em> This will block even if the underlying Flowable is asynchronous. + * Consumes the upstream {@code Flowable} in a blocking fashion and invokes the given + * {@code Consumer} with each upstream item on the <em>current thread</em> until the + * upstream terminates. * <p> * <img width="640" height="330" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.forEach.png" alt=""> * <p> - * This is similar to {@link Flowable#subscribe(Subscriber)}, but it blocks. Because it blocks it does not - * need the {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)} methods. If the - * underlying Flowable terminates with an error, rather than calling {@code onError}, this method will - * throw an exception. - * - * <p>The difference between this method and {@link #subscribe(Consumer)} is that the {@code onNext} action - * is executed on the emission thread instead of the current thread. + * <em>Note:</em> the method will only return if the upstream terminates or the current + * thread is interrupted. + * <p> + * This method executes the {@code Consumer} on the current thread while + * {@link #subscribe(Consumer)} executes the consumer on the original caller thread of the + * sequence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingForEach} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param onNext @@ -5389,6 +5839,10 @@ public final Iterable<T> blockingIterable(int bufferSize) { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingLast} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @return the last item emitted by this {@code Flowable} @@ -5420,6 +5874,10 @@ public final T blockingLast() { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingLast} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param defaultItem @@ -5527,6 +5985,10 @@ public final Iterable<T> blockingNext() { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @return the single item emitted by this {@code Flowable} @@ -5551,6 +6013,10 @@ public final T blockingSingle() { * (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param defaultItem @@ -5567,15 +6033,16 @@ public final T blockingSingle(T defaultItem) { } /** - * Returns a {@link Future} representing the single value emitted by this {@code Flowable}. + * Returns a {@link Future} representing the only value emitted by this {@code Flowable}. + * <p> + * <img width="640" height="324" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/Flowable.toFuture.png" alt=""> * <p> * If the {@link Flowable} emits more than one item, {@link java.util.concurrent.Future} will receive an - * {@link java.lang.IllegalArgumentException}. If the {@link Flowable} is empty, {@link java.util.concurrent.Future} - * will receive an {@link java.util.NoSuchElementException}. + * {@link java.lang.IndexOutOfBoundsException}. If the {@link Flowable} is empty, {@link java.util.concurrent.Future} + * will receive a {@link java.util.NoSuchElementException}. The {@code Flowable} source has to terminate in order + * for the returned {@code Future} to terminate as well. * <p> * If the {@code Flowable} may emit more than one item, use {@code Flowable.toList().toFuture()}. - * <p> - * <img width="640" height="395" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toFuture.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner @@ -5595,7 +6062,11 @@ public final Future<T> toFuture() { } /** - * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + * Runs the source Flowable to a terminal event, ignoring any values and rethrowing any exception. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner @@ -5604,6 +6075,9 @@ public final Future<T> toFuture() { * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @since 2.0 + * @see #blockingSubscribe(Consumer) + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) @@ -5617,7 +6091,12 @@ public final void blockingSubscribe() { * If the Flowable emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner @@ -5627,6 +6106,8 @@ public final void blockingSubscribe() { * </dl> * @param onNext the callback action for each source value * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) @@ -5636,6 +6117,42 @@ public final void blockingSubscribe(Consumer<? super T> onNext) { /** * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param bufferSize the size of the buffer + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer<? super T> onNext, int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION, bufferSize); + } + + /** + * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner @@ -5646,6 +6163,7 @@ public final void blockingSubscribe(Consumer<? super T> onNext) { * @param onNext the callback action for each source value * @param onError the callback action for an error event * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) @@ -5653,9 +6171,39 @@ public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super FlowableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION); } + /** + * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param bufferSize the size of the buffer + * @since 2.2 + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, + int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION, bufferSize); + } /** * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the source {@code Flowable} in an unbounded manner @@ -5675,8 +6223,40 @@ public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super } /** - * Subscribes to the source and calls the Subscriber methods <strong>on the current thread</strong>. + * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param bufferSize the size of the buffer + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, + int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, onComplete, bufferSize); + } + + /** + * Subscribes to the source and calls the {@link Subscriber} methods <strong>on the current thread</strong>. * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally, with an error or the {@code Subscriber} cancels the {@link Subscription} it receives via + * {@link Subscriber#onSubscribe(Subscription)}. + * Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The supplied {@code Subscriber} determines how backpressure is applied.</dd> @@ -5696,8 +6276,9 @@ public final void blockingSubscribe(Subscriber<? super T> subscriber) { /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each containing {@code count} items. When the source - * Publisher completes or encounters an error, the resulting Publisher emits the current buffer and - * propagates the notification from the source Publisher. + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer3.png" alt=""> * <dl> @@ -5725,8 +6306,9 @@ public final Flowable<List<T>> buffer(int count) { /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits buffers every {@code skip} items, each containing {@code count} items. When the source - * Publisher completes or encounters an error, the resulting Publisher emits the current buffer and - * propagates the notification from the source Publisher. + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer4.png" alt=""> * <dl> @@ -5758,8 +6340,9 @@ public final Flowable<List<T>> buffer(int count, int skip) { /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits buffers every {@code skip} items, each containing {@code count} items. When the source - * Publisher completes or encounters an error, the resulting Publisher emits the current buffer and - * propagates the notification from the source Publisher. + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer4.png" alt=""> * <dl> @@ -5786,6 +6369,7 @@ public final Flowable<List<T>> buffer(int count, int skip) { * @see <a href="http://reactivex.io/documentation/operators/buffer.html">ReactiveX operators documentation: Buffer</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U extends Collection<? super T>> Flowable<U> buffer(int count, int skip, Callable<U> bufferSupplier) { @@ -5798,8 +6382,9 @@ public final <U extends Collection<? super T>> Flowable<U> buffer(int count, int /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each containing {@code count} items. When the source - * Publisher completes or encounters an error, the resulting Publisher emits the current buffer and - * propagates the notification from the source Publisher. + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer3.png" alt=""> * <dl> @@ -5832,8 +6417,9 @@ public final <U extends Collection<? super T>> Flowable<U> buffer(int count, Cal * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument. It emits * each buffer after a fixed timespan, specified by the {@code timespan} argument. When the source - * Publisher completes or encounters an error, the resulting Publisher emits the current buffer and - * propagates the notification from the source Publisher. + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.png" alt=""> * <dl> @@ -5865,8 +6451,10 @@ public final Flowable<List<T>> buffer(long timespan, long timeskip, TimeUnit uni * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the - * {@code timespan} argument. When the source Publisher completes or encounters an error, the resulting - * Publisher emits the current buffer and propagates the notification from the source Publisher. + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.s.png" alt=""> * <dl> @@ -5874,7 +6462,7 @@ public final Flowable<List<T>> buffer(long timespan, long timeskip, TimeUnit uni * <dd>This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -5900,8 +6488,10 @@ public final Flowable<List<T>> buffer(long timespan, long timeskip, TimeUnit uni * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the - * {@code timespan} argument. When the source Publisher completes or encounters an error, the resulting - * Publisher emits the current buffer and propagates the notification from the source Publisher. + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.s.png" alt=""> * <dl> @@ -5909,7 +6499,7 @@ public final Flowable<List<T>> buffer(long timespan, long timeskip, TimeUnit uni * <dd>This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <U> the collection subclass type to buffer into @@ -5929,6 +6519,7 @@ public final Flowable<List<T>> buffer(long timespan, long timeskip, TimeUnit uni * @see <a href="http://reactivex.io/documentation/operators/buffer.html">ReactiveX operators documentation: Buffer</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final <U extends Collection<? super T>> Flowable<U> buffer(long timespan, long timeskip, TimeUnit unit, @@ -5942,8 +6533,10 @@ public final <U extends Collection<? super T>> Flowable<U> buffer(long timespan, /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the - * {@code timespan} argument. When the source Publisher completes or encounters an error, the resulting - * Publisher emits the current buffer and propagates the notification from the source Publisher. + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer5.png" alt=""> * <dl> @@ -5974,8 +6567,9 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit) { * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached - * first). When the source Publisher completes or encounters an error, the resulting Publisher emits the - * current buffer and propagates the notification from the source Publisher. + * first). When the source Publisher completes, the resulting Publisher emits the current buffer and propagates the + * notification from the source Publisher. Note that if the source Publisher issues an onError notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.png" alt=""> * <dl> @@ -6009,9 +6603,10 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit, int count) { * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by - * the {@code count} argument (whichever is reached first). When the source Publisher completes or - * encounters an error, the resulting Publisher emits the current buffer and propagates the notification - * from the source Publisher. + * the {@code count} argument (whichever is reached first). When the source Publisher completes, the resulting + * Publisher emits the current buffer and propagates the notification from the source Publisher. Note that if the + * source Publisher issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.s.png" alt=""> * <dl> @@ -6019,7 +6614,7 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit, int count) { * <dd>This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -6047,9 +6642,10 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit, Scheduler sc * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by - * the {@code count} argument (whichever is reached first). When the source Publisher completes or - * encounters an error, the resulting Publisher emits the current buffer and propagates the notification - * from the source Publisher. + * the {@code count} argument (whichever is reached first). When the source Publisher completes, the resulting + * Publisher emits the current buffer and propagates the notification from the source Publisher. Note that if the + * source Publisher issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.s.png" alt=""> * <dl> @@ -6057,7 +6653,7 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit, Scheduler sc * <dd>This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <U> the collection subclass type to buffer into @@ -6098,9 +6694,10 @@ public final <U extends Collection<? super T>> Flowable<U> buffer( /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the - * {@code timespan} argument and on the specified {@code scheduler}. When the source Publisher completes or - * encounters an error, the resulting Publisher emits the current buffer and propagates the notification - * from the source Publisher. + * {@code timespan} argument and on the specified {@code scheduler}. When the source Publisher completes, the + * resulting Publisher emits the current buffer and propagates the notification from the source Publisher. Note that + * if the source Publisher issues an onError notification the event is passed on immediately without first emitting + * the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer5.s.png" alt=""> * <dl> @@ -6108,7 +6705,7 @@ public final <U extends Collection<? super T>> Flowable<U> buffer( * <dd>This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} * upstream and does not obey downstream requests.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -6132,7 +6729,9 @@ public final Flowable<List<T>> buffer(long timespan, TimeUnit unit, Scheduler sc /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits buffers that it creates when the specified {@code openingIndicator} Publisher emits an - * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. + * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. If any of the source + * Publisher, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the event is passed + * on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="470" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer2.png" alt=""> * <dl> @@ -6166,7 +6765,9 @@ public final <TOpening, TClosing> Flowable<List<T>> buffer( /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits buffers that it creates when the specified {@code openingIndicator} Publisher emits an - * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. + * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. If any of the source + * Publisher, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the event is passed + * on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="470" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer2.png" alt=""> * <dl> @@ -6212,7 +6813,8 @@ public final <TOpening, TClosing, U extends Collection<? super T>> Flowable<U> b * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it is instead controlled by the {@code Publisher} @@ -6245,7 +6847,8 @@ public final <B> Flowable<List<T>> buffer(Publisher<B> boundaryIndicator) { * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it is instead controlled by the {@code Publisher} @@ -6281,7 +6884,8 @@ public final <B> Flowable<List<T>> buffer(Publisher<B> boundaryIndicator, final * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it is instead controlled by the {@code Publisher} @@ -6316,9 +6920,12 @@ public final <B, U extends Collection<? super T>> Flowable<U> buffer(Publisher<B /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a - * new buffer whenever the Publisher produced by the specified {@code closingIndicator} emits an item. + * new buffer whenever the Publisher produced by the specified {@code boundaryIndicatorSupplier} emits an item. * <p> * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer1.png" alt=""> + * <p> + * If either the source {@code Publisher} or the boundary {@code Publisher} issues an {@code onError} notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it is instead controlled by the given Publishers and @@ -6330,7 +6937,7 @@ public final <B, U extends Collection<? super T>> Flowable<U> buffer(Publisher<B * @param <B> the value type of the boundary-providing Publisher * @param boundaryIndicatorSupplier * a {@link Callable} that produces a Publisher that governs the boundary between buffers. - * Whenever the source {@code Publisher} emits an item, {@code buffer} emits the current buffer and + * Whenever the supplied {@code Publisher} emits an item, {@code buffer} emits the current buffer and * begins to fill a new one * @return a Flowable that emits a connected, non-overlapping buffer of items from the source Publisher * each time the Publisher created with the {@code closingIndicator} argument emits an item @@ -6341,15 +6948,17 @@ public final <B, U extends Collection<? super T>> Flowable<U> buffer(Publisher<B @SchedulerSupport(SchedulerSupport.NONE) public final <B> Flowable<List<T>> buffer(Callable<? extends Publisher<B>> boundaryIndicatorSupplier) { return buffer(boundaryIndicatorSupplier, ArrayListSupplier.<T>asCallable()); - } /** * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting * Publisher emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a - * new buffer whenever the Publisher produced by the specified {@code closingIndicator} emits an item. + * new buffer whenever the Publisher produced by the specified {@code boundaryIndicatorSupplier} emits an item. * <p> * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer1.png" alt=""> + * <p> + * If either the source {@code Publisher} or the boundary {@code Publisher} issues an {@code onError} notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it is instead controlled by the given Publishers and @@ -6362,7 +6971,7 @@ public final <B> Flowable<List<T>> buffer(Callable<? extends Publisher<B>> bound * @param <B> the value type of the boundary-providing Publisher * @param boundaryIndicatorSupplier * a {@link Callable} that produces a Publisher that governs the boundary between buffers. - * Whenever the source {@code Publisher} emits an item, {@code buffer} emits the current buffer and + * Whenever the supplied {@code Publisher} emits an item, {@code buffer} emits the current buffer and * begins to fill a new one * @param bufferSupplier * a factory function that returns an instance of the collection subclass to be used and returned @@ -6524,6 +7133,7 @@ public final Flowable<T> cacheWithInitialCapacity(int initialCapacity) { * @see <a href="http://reactivex.io/documentation/operators/map.html">ReactiveX operators documentation: Map</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> cast(final Class<U> clazz) { @@ -6532,12 +7142,16 @@ public final <U> Flowable<U> cast(final Class<U> clazz) { } /** - * Collects items emitted by the source Publisher into a single mutable data structure and returns + * Collects items emitted by the finite source Publisher into a single mutable data structure and returns * a Single that emits this structure. * <p> * <img width="640" height="330" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collect.png" alt=""> * <p> * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure because by intent it will receive all values and reduce @@ -6557,6 +7171,7 @@ public final <U> Flowable<U> cast(final Class<U> clazz) { * @see <a href="http://reactivex.io/documentation/operators/reduce.html">ReactiveX operators documentation: Reduce</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<U> collect(Callable<? extends U> initialItemSupplier, BiConsumer<? super U, ? super T> collector) { @@ -6566,12 +7181,16 @@ public final <U> Single<U> collect(Callable<? extends U> initialItemSupplier, Bi } /** - * Collects items emitted by the source Publisher into a single mutable data structure and returns + * Collects items emitted by the finite source Publisher into a single mutable data structure and returns * a Single that emits this structure. * <p> * <img width="640" height="330" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collect.png" alt=""> * <p> * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure because by intent it will receive all values and reduce @@ -6591,6 +7210,7 @@ public final <U> Single<U> collect(Callable<? extends U> initialItemSupplier, Bi * @see <a href="http://reactivex.io/documentation/operators/reduce.html">ReactiveX operators documentation: Reduce</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<U> collectInto(final U initialItem, BiConsumer<? super U, ? super T> collector) { @@ -6647,7 +7267,7 @@ public final <R> Flowable<R> compose(FlowableTransformer<? super T, ? extends R> * * @param <R> the type of the inner Publisher sources and thus the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @return a Flowable that emits the result of applying the transformation function to each item emitted * by the source Publisher and concatenating the Publishers obtained from this transformation @@ -6679,7 +7299,7 @@ public final <R> Flowable<R> concatMap(Function<? super T, ? extends Publisher<? * * @param <R> the type of the inner Publisher sources and thus the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param prefetch * the number of elements to prefetch from the current Flowable @@ -6688,6 +7308,7 @@ public final <R> Flowable<R> concatMap(Function<? super T, ? extends Publisher<? * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> concatMap(Function<? super T, ? extends Publisher<? extends R>> mapper, int prefetch) { @@ -6704,6 +7325,170 @@ public final <R> Flowable<R> concatMap(Function<? super T, ? extends Publisher<? return RxJavaPlugins.onAssembly(new FlowableConcatMap<T, R>(this, mapper, prefetch, ErrorMode.IMMEDIATE)); } + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletable(Function<? super T, ? extends CompletableSource> mapper) { + return concatMapCompletable(mapper, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletableDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletable(Function<? super T, ? extends CompletableSource> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapCompletable<T>(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper) { + return concatMapCompletableDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @return a new Completable instance + * @see #concatMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper, boolean tillTheEnd) { + return concatMapCompletableDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapCompletable<T>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + /** * Maps each of the items into a Publisher, subscribes to them one after the other, * one at a time and emits their values in order @@ -6755,10 +7540,11 @@ public final <R> Flowable<R> concatMapDelayError(Function<? super T, ? extends P * the number of elements to prefetch from the current Flowable * @param tillTheEnd * if true, all errors from the outer and inner Publisher sources are delayed until the end, - * if false, an error from the main source is signalled when the current Publisher source terminates + * if false, an error from the main source is signaled when the current Publisher source terminates * @return the new Publisher instance with the concatenation behavior */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> concatMapDelayError(Function<? super T, ? extends Publisher<? extends R>> mapper, @@ -6776,7 +7562,6 @@ public final <R> Flowable<R> concatMapDelayError(Function<? super T, ? extends P return RxJavaPlugins.onAssembly(new FlowableConcatMap<T, R>(this, mapper, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); } - /** * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single * Publisher. @@ -6827,6 +7612,7 @@ public final <R> Flowable<R> concatMapEager(Function<? super T, ? extends Publis * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> concatMapEager(Function<? super T, ? extends Publisher<? extends R>> mapper, @@ -6856,7 +7642,7 @@ public final <R> Flowable<R> concatMapEager(Function<? super T, ? extends Publis * eagerly concatenated * @param tillTheEnd * if true, all errors from the outer and inner Publisher sources are delayed until the end, - * if false, an error from the main source is signalled when the current Publisher source terminates + * if false, an error from the main source is signaled when the current Publisher source terminates * @return the new Publisher instance with the specified concatenation behavior * @since 2.0 */ @@ -6896,6 +7682,7 @@ public final <R> Flowable<R> concatMapEagerDelayError(Function<? super T, ? exte * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> concatMapEagerDelayError(Function<? super T, ? extends Publisher<? extends R>> mapper, @@ -6909,7 +7696,6 @@ public final <R> Flowable<R> concatMapEagerDelayError(Function<? super T, ? exte /** * Returns a Flowable that concatenate each item emitted by the source Publisher with the values in an * Iterable corresponding to that item that is generated by a selector. - * <p> * * <dl> * <dt><b>Backpressure:</b></dt> @@ -6939,7 +7725,6 @@ public final <U> Flowable<U> concatMapIterable(Function<? super T, ? extends Ite /** * Returns a Flowable that concatenate each item emitted by the source Publisher with the values in an * Iterable corresponding to that item that is generated by a selector. - * <p> * * <dl> * <dt><b>Backpressure:</b></dt> @@ -6962,6 +7747,7 @@ public final <U> Flowable<U> concatMapIterable(Function<? super T, ? extends Ite * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> concatMapIterable(final Function<? super T, ? extends Iterable<? extends U>> mapper, int prefetch) { @@ -6971,8 +7757,368 @@ public final <U> Flowable<U> concatMapIterable(final Function<? super T, ? exten } /** - * Returns a Flowable that emits the items emitted from the current Publisher, then the next, one after - * the other, without interleaving them. + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Flowable} or the current inner {@code MaybeSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapMaybeDelayError(Function) + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + return concatMapMaybe(mapper, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Flowable} or the current inner {@code MaybeSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Flowable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapMaybe<T, R>(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + return concatMapMaybeDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @return a new Flowable instance + * @see #concatMapMaybe(Function, int) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean tillTheEnd) { + return concatMapMaybeDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Flowable instance + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapMaybe<T, R>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Flowable} or the current inner {@code SingleSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapSingleDelayError(Function) + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapSingle(Function<? super T, ? extends SingleSource<? extends R>> mapper) { + return concatMapSingle(mapper, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Flowable} or the current inner {@code SingleSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Flowable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapSingle(Function<? super T, ? extends SingleSource<? extends R>> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapSingle<T, R>(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and delays all errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper) { + return concatMapSingleDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays all errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @return a new Flowable instance + * @see #concatMapSingle(Function, int) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean tillTheEnd) { + return concatMapSingleDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Flowable instance + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapSingle<T, R>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Returns a Flowable that emits the items emitted from the current Publisher, then the next, one after + * the other, without interleaving them. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> * <dl> @@ -6991,6 +8137,7 @@ public final <U> Flowable<U> concatMapIterable(final Function<? super T, ? exten * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> concatWith(Publisher<? extends T> other) { @@ -6998,6 +8145,83 @@ public final Flowable<T> concatWith(Publisher<? extends T> other) { return concat(this, other); } + /** + * Returns a {@code Flowable} that emits the items from this {@code Flowable} followed by the success item or error event + * of the other {@link SingleSource}. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator supports backpressure and makes sure the success item of the other {@code SingleSource} + * is only emitted when there is a demand for it.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the SingleSource whose signal should be emitted after this {@code Flowable} completes normally. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> concatWith(@NonNull SingleSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithSingle<T>(this, other)); + } + + /** + * Returns a {@code Flowable} that emits the items from this {@code Flowable} followed by the success item or terminal events + * of the other {@link MaybeSource}. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator supports backpressure and makes sure the success item of the other {@code MaybeSource} + * is only emitted when there is a demand for it.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the MaybeSource whose signal should be emitted after this Flowable completes normally. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> concatWith(@NonNull MaybeSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithMaybe<T>(this, other)); + } + + /** + * Returns a {@code Flowable} that emits items from this {@code Flowable} and when it completes normally, the + * other {@link CompletableSource} is subscribed to and the returned {@code Flowable} emits its terminal events. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator does not interfere with backpressure between the current Flowable and the + * downstream consumer (i.e., acts as pass-through). When the operator switches to the + * {@code Completable}, backpressure is no longer present because {@code Completable} doesn't + * have items to apply backpressure to.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to subscribe to once the current {@code Flowable} completes normally + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> concatWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithCompletable<T>(this, other)); + } + /** * Returns a Single that emits a Boolean that indicates whether the source Publisher emitted a * specified item. @@ -7018,6 +8242,7 @@ public final Flowable<T> concatWith(Publisher<? extends T> other) { * @see <a href="http://reactivex.io/documentation/operators/contains.html">ReactiveX operators documentation: Contains</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<Boolean> contains(final Object item) { @@ -7041,7 +8266,6 @@ public final Single<Boolean> contains(final Object item) { * @return a Single that emits a single item: the number of items emitted by the source Publisher as a * 64-bit Long item * @see <a href="http://reactivex.io/documentation/operators/count.html">ReactiveX operators documentation: Count</a> - * @see #count() */ @CheckReturnValue @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @@ -7055,6 +8279,14 @@ public final Single<Long> count() { * source Publisher that are followed by another item within a computed debounce duration. * <p> * <img width="640" height="425" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.f.png" alt=""> + * <p> + * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code Publisher} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get cancelled, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses the {@code debounceSelector} to mark @@ -7073,6 +8305,7 @@ public final Single<Long> count() { * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Backpressure">RxJava wiki: Backpressure</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> debounce(Function<? super T, ? extends Publisher<U>> debounceIndicator) { @@ -7090,25 +8323,25 @@ public final <U> Flowable<T> debounce(Function<? super T, ? extends Publisher<U> * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.png" alt=""> * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>This version of {@code debounce} operates by default on the {@code computation} {@link Scheduler}.</dd> + * <dd>{@code debounce} operates by default on the {@code computation} {@link Scheduler}.</dd> * </dl> * * @param timeout - * the time each item has to be "the most recent" of those emitted by the source Publisher to - * ensure that it's not dropped + * the length of the window of time that must pass after the emission of an item from the source + * Publisher in which that Publisher emits no items in order for the item to be emitted by the + * resulting Publisher * @param unit - * the {@link TimeUnit} for the timeout + * the unit of time for the specified {@code timeout} * @return a Flowable that filters out items from the source Publisher that are too quickly followed by * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> @@ -7132,25 +8365,24 @@ public final Flowable<T> debounce(long timeout, TimeUnit unit) { * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.s.png" alt=""> * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout * the time each item has to be "the most recent" of those emitted by the source Publisher to * ensure that it's not dropped * @param unit - * the unit of time for the specified timeout + * the unit of time for the specified {@code timeout} * @param scheduler * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each * item @@ -7161,6 +8393,7 @@ public final Flowable<T> debounce(long timeout, TimeUnit unit) { * @see #throttleWithTimeout(long, TimeUnit, Scheduler) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> debounce(long timeout, TimeUnit unit, Scheduler scheduler) { @@ -7178,7 +8411,7 @@ public final Flowable<T> debounce(long timeout, TimeUnit unit, Scheduler schedul * <dt><b>Backpressure:</b></dt> * <dd>If the source {@code Publisher} is empty, this operator is guaranteed to honor backpressure from downstream. * If the source {@code Publisher} is non-empty, it is expected to honor backpressure as well; if the rule is violated, - * a {@code MissingBackpressureException} <em>may</em> get signalled somewhere downstream. + * a {@code MissingBackpressureException} <em>may</em> get signaled somewhere downstream. * </dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code defaultIfEmpty} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7191,10 +8424,11 @@ public final Flowable<T> debounce(long timeout, TimeUnit unit, Scheduler schedul * @see <a href="http://reactivex.io/documentation/operators/defaultifempty.html">ReactiveX operators documentation: DefaultIfEmpty</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> defaultIfEmpty(T defaultItem) { - ObjectHelper.requireNonNull(defaultItem, "item is null"); + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); return switchIfEmpty(just(defaultItem)); } @@ -7226,6 +8460,7 @@ public final Flowable<T> defaultIfEmpty(T defaultItem) { * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> delay(final Function<? super T, ? extends Publisher<U>> itemDelayIndicator) { @@ -7261,7 +8496,7 @@ public final Flowable<T> delay(long delay, TimeUnit unit) { /** * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a - * specified delay. Error notifications from the source Publisher are not delayed. + * specified delay. If {@code delayError} is true, error notifications will also be delayed. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.png" alt=""> * <dl> @@ -7276,8 +8511,8 @@ public final Flowable<T> delay(long delay, TimeUnit unit) { * @param unit * the {@link TimeUnit} in which {@code period} is defined * @param delayError - * if true, the upstream exception is signalled with the given delay, after all preceding normal elements, - * if false, the upstream exception is signalled immediately + * if true, the upstream exception is signaled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signaled immediately * @return the source Publisher shifted in time by the specified delay * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> */ @@ -7297,7 +8532,7 @@ public final Flowable<T> delay(long delay, TimeUnit unit, boolean delayError) { * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -7318,14 +8553,14 @@ public final Flowable<T> delay(long delay, TimeUnit unit, Scheduler scheduler) { /** * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a - * specified delay. Error notifications from the source Publisher are not delayed. + * specified delay. If {@code delayError} is true, error notifications will also be delayed. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.s.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -7335,12 +8570,13 @@ public final Flowable<T> delay(long delay, TimeUnit unit, Scheduler scheduler) { * @param scheduler * the {@link Scheduler} to use for delaying * @param delayError - * if true, the upstream exception is signalled with the given delay, after all preceding normal elements, - * if false, the upstream exception is signalled immediately + * if true, the upstream exception is signaled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signaled immediately * @return the source Publisher shifted in time by the specified delay * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> delay(long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { @@ -7393,7 +8629,6 @@ public final <U, V> Flowable<T> delay(Publisher<U> subscriptionIndicator, /** * Returns a Flowable that delays the subscription to this Publisher * until the other Publisher emits an element or completes normally. - * <p> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator forwards the backpressure requests to this Publisher once @@ -7410,6 +8645,7 @@ public final <U, V> Flowable<T> delay(Publisher<U> subscriptionIndicator, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> delaySubscription(Publisher<U> subscriptionIndicator) { @@ -7451,7 +8687,7 @@ public final Flowable<T> delaySubscription(long delay, TimeUnit unit) { * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -7477,6 +8713,27 @@ public final Flowable<T> delaySubscription(long delay, TimeUnit unit, Scheduler * represent. * <p> * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/dematerialize.png" alt=""> + * <p> + * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Flowable cancels the flow and terminates with that type of terminal event: + * <pre><code> + * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2)) + * .doOnCancel(() -> System.out.println("Cancelled!")); + * .dematerialize() + * .test() + * .assertResult(1); + * </code></pre> + * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + * <pre><code> + * Flowable.just(createOnNext(1), createOnNext(2)) + * .dematerialize() + * .test() + * .assertResult(1, 2); + * </code></pre> + * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(Publisher)} + * with a {@link #never()} source. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7489,20 +8746,96 @@ public final Flowable<T> delaySubscription(long delay, TimeUnit unit, Scheduler * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects * emitted by the source Publisher * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Dematerialize</a> + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. */ @CheckReturnValue - @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) public final <T2> Flowable<T2> dematerialize() { - @SuppressWarnings("unchecked") - Flowable<Notification<T2>> m = (Flowable<Notification<T2>>)this; - return RxJavaPlugins.onAssembly(new FlowableDematerialize<T2>(m)); + return RxJavaPlugins.onAssembly(new FlowableDematerialize(this, Functions.identity())); } /** - * Returns a Flowable that emits all items emitted by the source Publisher that are distinct. + * Returns a Flowable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Subscriber} signal types. + * <p> + * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/dematerialize.png" alt=""> + * <p> + * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification<T>}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + * <p> + * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Flowable cancels of the flow and terminates with that type of terminal event: + * <pre><code> + * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2)) + * .doOnCancel(() -> System.out.println("Canceled!")); + * .dematerialize(notification -> notification) + * .test() + * .assertResult(1); + * </code></pre> + * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + * <pre><code> + * Flowable.just(createOnNext(1), createOnNext(2)) + * .dematerialize(notification -> notification) + * .test() + * .assertResult(1, 2); + * </code></pre> + * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(Publisher)} + * with a {@link #never()} source. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code dematerialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @param <R> the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Subscriber} event to the downstream. + * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source Flowable + * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Dematerialize</a> + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final <R> Flowable<R> dematerialize(Function<? super T, Notification<R>> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new FlowableDematerialize<T, R>(this, selector)); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct + * based on {@link Object#equals(Object)} comparison. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.png" alt=""> + * <p> + * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between items as the default Java implementation only considers reference equivalence. + * <p> + * By default, {@code distinct()} uses an internal {@link java.util.HashSet} per Subscriber to remember + * previously seen items and uses {@link java.util.Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + * <p> + * Note that this internal {@code HashSet} may grow unbounded as items won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct elements) may lead + * to {@code OutOfMemoryError}. + * <p> + * Customizing the retention policy can happen only by providing a custom {@link java.util.Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7514,6 +8847,8 @@ public final <T2> Flowable<T2> dematerialize() { * @return a Flowable that emits only those items emitted by the source Publisher that are distinct from * each other * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinct(Function) + * @see #distinct(Function, Callable) */ @SuppressWarnings({ "rawtypes", "unchecked" }) @CheckReturnValue @@ -7525,9 +8860,24 @@ public final Flowable<T> distinct() { /** * Returns a Flowable that emits all items emitted by the source Publisher that are distinct according - * to a key selector function. + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + * <p> + * By default, {@code distinct()} uses an internal {@link java.util.HashSet} per Subscriber to remember + * previously seen keys and uses {@link java.util.Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + * <p> + * Note that this internal {@code HashSet} may grow unbounded as keys won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct keys) may lead + * to {@code OutOfMemoryError}. + * <p> + * Customizing the retention policy can happen only by providing a custom {@link java.util.Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7542,6 +8892,7 @@ public final Flowable<T> distinct() { * is distinct from another one or not * @return a Flowable that emits those items emitted by the source Publisher that have distinct keys * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinct(Function, Callable) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -7552,9 +8903,13 @@ public final <K> Flowable<T> distinct(Function<? super T, K> keySelector) { /** * Returns a Flowable that emits all items emitted by the source Publisher that are distinct according - * to a key selector function. + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7585,9 +8940,25 @@ public final <K> Flowable<T> distinct(Function<? super T, K> keySelector, /** * Returns a Flowable that emits all items emitted by the source Publisher that are distinct from their - * immediate predecessors. + * immediate predecessors based on {@link Object#equals(Object)} comparison. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.png" alt=""> + * <p> + * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} to provide + * a meaningful comparison between items as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code T} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the class {@code T}. + * <p> + * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7599,6 +8970,7 @@ public final <K> Flowable<T> distinct(Function<? super T, K> keySelector, * @return a Flowable that emits those items from the source Publisher that are distinct from their * immediate predecessors * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinctUntilChanged(BiPredicate) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -7609,9 +8981,27 @@ public final Flowable<T> distinctUntilChanged() { /** * Returns a Flowable that emits all items emitted by the source Publisher that are distinct from their - * immediate predecessors, according to a key selector function. + * immediate predecessors, according to a key selector function and based on {@link Object#equals(Object)} comparison + * of those objects returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code K} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the item class {@code T} (for which the keys can be + * derived via a similar selector). + * <p> + * Note that the operator always retains the latest key from upstream regardless of the comparison result + * and uses it in the next comparison with the next key derived from the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7641,6 +9031,16 @@ public final <K> Flowable<T> distinctUntilChanged(Function<? super T, K> keySele * immediate predecessors when compared with each other via the provided comparator function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.png" alt=""> + * <p> + * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s @@ -7665,7 +9065,7 @@ public final Flowable<T> distinctUntilChanged(BiPredicate<? super T, ? super T> } /** - * Calls the specified action after this Flowable signals onError or onCompleted or gets cancelled by + * Calls the specified action after this Flowable signals onError or onCompleted or gets canceled by * the downstream. * <p>In case of a race between a terminal event and a cancellation, the provided {@code onFinally} action * is executed once per subscription. @@ -7682,7 +9082,7 @@ public final Flowable<T> distinctUntilChanged(BiPredicate<? super T, ? super T> * synchronous or asynchronous queue-fusion.</dd> * </dl> * <p>History: 2.0.1 - experimental - * @param onFinally the action called when this Flowable terminates or gets cancelled + * @param onFinally the action called when this Flowable terminates or gets canceled * @return the new Flowable instance * @since 2.1 */ @@ -7753,7 +9153,7 @@ public final Flowable<T> doAfterTerminate(Action onAfterTerminate) { * Calls the cancel {@code Action} if the downstream cancels the sequence. * <p> * The action is shared between subscriptions and thus may be called concurrently from multiple - * threads; the action must be thread safe. + * threads; the action must be thread-safe. * <p> * If the action throws a runtime exception, that exception is rethrown by the {@code onCancel()} call, * sometimes as a {@code CompositeException} if there were multiple exceptions along the way. @@ -7770,7 +9170,7 @@ public final Flowable<T> doAfterTerminate(Action onAfterTerminate) { * </dl> * * @param onCancel - * the action that gets called when the source {@code Publisher}'s Subscription is cancelled + * the action that gets called when the source {@code Publisher}'s Subscription is canceled * @return the source {@code Publisher} modified so as to call this Action when appropriate * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @@ -7823,6 +9223,7 @@ public final Flowable<T> doOnComplete(Action onComplete) { * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) private Flowable<T> doOnEach(Consumer<? super T> onNext, Consumer<? super Throwable> onError, @@ -7852,10 +9253,11 @@ private Flowable<T> doOnEach(Consumer<? super T> onNext, Consumer<? super Throwa * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> doOnEach(final Consumer<? super Notification<T>> onNotification) { - ObjectHelper.requireNonNull(onNotification, "consumer is null"); + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); return doOnEach( Functions.notificationOnNext(onNotification), Functions.notificationOnError(onNotification), @@ -7888,6 +9290,7 @@ public final Flowable<T> doOnEach(final Consumer<? super Notification<T>> onNoti * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> doOnEach(final Subscriber<? super T> subscriber) { @@ -7950,6 +9353,7 @@ public final Flowable<T> doOnError(Consumer<? super Throwable> onError) { * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> doOnLifecycle(final Consumer<? super Subscription> onSubscribe, @@ -8101,7 +9505,7 @@ public final Maybe<T> elementAt(long index) { } /** - * Returns a Flowable that emits the item found at a specified index in a sequence of emissions from + * Returns a Single that emits the item found at a specified index in a sequence of emissions from * this Flowable, or a default item if that index is out of range. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAtOrDefault.png" alt=""> @@ -8117,13 +9521,14 @@ public final Maybe<T> elementAt(long index) { * the zero-based index of the item to retrieve * @param defaultItem * the default item - * @return a Flowable that emits the item at the specified position in the sequence emitted by the source + * @return a Single that emits the item at the specified position in the sequence emitted by the source * Publisher, or the default item if that index is outside the bounds of the source sequence * @throws IndexOutOfBoundsException * if {@code index} is less than 0 * @see <a href="http://reactivex.io/documentation/operators/elementat.html">ReactiveX operators documentation: ElementAt</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> elementAt(long index, T defaultItem) { @@ -8135,7 +9540,7 @@ public final Single<T> elementAt(long index, T defaultItem) { } /** - * Returns a Flowable that emits the item found at a specified index in a sequence of emissions from + * Returns a Single that emits the item found at a specified index in a sequence of emissions from * this Flowable or signals a {@link NoSuchElementException} if this Flowable has fewer elements than index. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAtOrDefault.png" alt=""> @@ -8149,7 +9554,7 @@ public final Single<T> elementAt(long index, T defaultItem) { * * @param index * the zero-based index of the item to retrieve - * @return a Flowable that emits the item at the specified position in the sequence emitted by the source + * @return a Single that emits the item at the specified position in the sequence emitted by the source * Publisher, or the default item if that index is outside the bounds of the source sequence * @throws IndexOutOfBoundsException * if {@code index} is less than 0 @@ -8185,6 +9590,7 @@ public final Single<T> elementAtOrError(long index) { * @see <a href="http://reactivex.io/documentation/operators/filter.html">ReactiveX operators documentation: Filter</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> filter(Predicate<? super T> predicate) { @@ -8282,7 +9688,7 @@ public final Single<T> firstOrError() { * * @param <R> the value type of the inner Publishers and the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @return a Flowable that emits the result of applying the transformation function to each item emitted * by the source Publisher and merging the results of the Publishers obtained from this @@ -8314,11 +9720,11 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * * @param <R> the value type of the inner Publishers and the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @return a Flowable that emits the result of applying the transformation function to each item emitted * by the source Publisher and merging the results of the Publishers obtained from this * transformation @@ -8336,7 +9742,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * by the source Publisher, where that function returns a Publisher, and then merging those resulting * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8350,7 +9756,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * * @param <R> the value type of the inner Publishers and the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param maxConcurrency * the maximum number of Publishers that may be subscribed to concurrently @@ -8372,7 +9778,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * by the source Publisher, where that function returns a Publisher, and then merging those resulting * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8386,13 +9792,13 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * * @param <R> the value type of the inner Publishers and the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param maxConcurrency * the maximum number of Publishers that may be subscribed to concurrently * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @return a Flowable that emits the result of applying the transformation function to each item emitted * by the source Publisher and merging the results of the Publishers obtained from this * transformation @@ -8411,7 +9817,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * by the source Publisher, where that function returns a Publisher, and then merging those resulting * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8425,13 +9831,13 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * * @param <R> the value type of the inner Publishers and the output type * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param maxConcurrency * the maximum number of Publishers that may be subscribed to concurrently * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @param bufferSize * the number of elements to prefetch from each inner Publisher * @return a Flowable that emits the result of applying the transformation function to each item emitted @@ -8441,6 +9847,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper, @@ -8489,6 +9896,7 @@ public final <R> Flowable<R> flatMap(Function<? super T, ? extends Publisher<? e * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMap( @@ -8505,7 +9913,7 @@ public final <R> Flowable<R> flatMap( * Returns a Flowable that applies a function to each item emitted or notification raised by the source * Publisher and then flattens the Publishers returned from these functions and emits the resulting items, * while limiting the maximum number of concurrent subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.nce.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8535,6 +9943,7 @@ public final <R> Flowable<R> flatMap( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMap( @@ -8611,7 +10020,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * returns an item to be emitted by the resulting Publisher * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @return a Flowable that emits the results of applying a function to a pair of values emitted by the * source Publisher and the collection Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> @@ -8628,7 +10037,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8653,7 +10062,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * the maximum number of Publishers that may be subscribed to concurrently * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @return a Flowable that emits the results of applying a function to a pair of values emitted by the * source Publisher and the collection Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> @@ -8671,7 +10080,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8696,7 +10105,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * the maximum number of Publishers that may be subscribed to concurrently * @param delayErrors * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate - * if false, the first one signalling an exception will terminate the whole sequence immediately + * if false, the first one signaling an exception will terminate the whole sequence immediately * @param bufferSize * the number of elements to prefetch from the inner Publishers. * @return a Flowable that emits the results of applying a function to a pair of values emitted by the @@ -8705,6 +10114,7 @@ public final <U, R> Flowable<R> flatMap(Function<? super T, ? extends Publisher< * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Flowable<R> flatMap(final Function<? super T, ? extends Publisher<? extends U>> mapper, @@ -8720,7 +10130,7 @@ public final <U, R> Flowable<R> flatMap(final Function<? super T, ? extends Publ * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent * subscriptions to these Publishers. - * <p> + * <!-- <p> --> * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> * <dl> * <dt><b>Backpressure:</b></dt> @@ -8781,7 +10191,7 @@ public final Completable flatMapCompletable(Function<? super T, ? extends Comple * <dl> * <dt><b>Backpressure:</b></dt> * <dd>If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. - * Otherwise the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8793,6 +10203,7 @@ public final Completable flatMapCompletable(Function<? super T, ? extends Comple * @return the new Completable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Completable flatMapCompletable(Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors, int maxConcurrency) { @@ -8857,6 +10268,7 @@ public final <U> Flowable<U> flatMapIterable(final Function<? super T, ? extends * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> flatMapIterable(final Function<? super T, ? extends Iterable<? extends U>> mapper, int bufferSize) { @@ -8893,6 +10305,7 @@ public final <U> Flowable<U> flatMapIterable(final Function<? super T, ? extends * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, V> Flowable<V> flatMapIterable(final Function<? super T, ? extends Iterable<? extends U>> mapper, @@ -8935,6 +10348,7 @@ public final <U, V> Flowable<V> flatMapIterable(final Function<? super T, ? exte * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, V> Flowable<V> flatMapIterable(final Function<? super T, ? extends Iterable<? extends U>> mapper, @@ -8945,8 +10359,8 @@ public final <U, V> Flowable<V> flatMapIterable(final Function<? super T, ? exte } /** - * Maps each element of the upstream Flowable into MaybeSources, subscribes to them and - * waits until the upstream and all MaybeSources complete. + * Maps each element of the upstream Flowable into MaybeSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Flowable sequence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the upstream in an unbounded manner.</dd> @@ -8965,12 +10379,13 @@ public final <R> Flowable<R> flatMapMaybe(Function<? super T, ? extends MaybeSou } /** - * Maps each element of the upstream Flowable into MaybeSources, subscribes to them and - * waits until the upstream and all MaybeSources complete, optionally delaying all errors. + * Maps each element of the upstream Flowable into MaybeSources, subscribes to at most + * {@code maxConcurrency} MaybeSources at a time and merges their onSuccess values, + * in no particular order, into a single Flowable sequence, optionally delaying all errors. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. - * Otherwise the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8983,6 +10398,7 @@ public final <R> Flowable<R> flatMapMaybe(Function<? super T, ? extends MaybeSou * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean delayErrors, int maxConcurrency) { @@ -8992,8 +10408,8 @@ public final <R> Flowable<R> flatMapMaybe(Function<? super T, ? extends MaybeSou } /** - * Maps each element of the upstream Flowable into SingleSources, subscribes to them and - * waits until the upstream and all SingleSources complete. + * Maps each element of the upstream Flowable into SingleSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Flowable sequence. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator consumes the upstream in an unbounded manner.</dd> @@ -9012,12 +10428,13 @@ public final <R> Flowable<R> flatMapSingle(Function<? super T, ? extends SingleS } /** - * Maps each element of the upstream Flowable into SingleSources, subscribes to them and - * waits until the upstream and all SingleSources complete, optionally delaying all errors. + * Maps each element of the upstream Flowable into SingleSources, subscribes to at most + * {@code maxConcurrency} SingleSources at a time and merges their onSuccess values, + * in no particular order, into a single Flowable sequence, optionally delaying all errors. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. - * Otherwise the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9030,6 +10447,7 @@ public final <R> Flowable<R> flatMapSingle(Function<? super T, ? extends SingleS * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMapSingle(Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean delayErrors, int maxConcurrency) { @@ -9053,7 +10471,7 @@ public final <R> Flowable<R> flatMapSingle(Function<? super T, ? extends SingleS * @param onNext * {@link Consumer} to execute for each item. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows canceling an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> @@ -9083,7 +10501,7 @@ public final Disposable forEach(Consumer<? super T> onNext) { * @param onNext * {@link Predicate} to execute for each item. * @return - * a {@link Disposable} that allows cancelling an asynchronous sequence + * a {@link Disposable} that allows canceling an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> @@ -9111,7 +10529,7 @@ public final Disposable forEachWhile(Predicate<? super T> onNext) { * @param onError * {@link Consumer} to execute when an error is emitted. * @return - * a {@link Disposable} that allows cancelling an asynchronous sequence + * a {@link Disposable} that allows canceling an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null @@ -9140,9 +10558,9 @@ public final Disposable forEachWhile(Predicate<? super T> onNext, Consumer<? sup * @param onError * {@link Consumer} to execute when an error is emitted. * @param onComplete - * {@link Action} to execute when completion is signalled. + * {@link Action} to execute when completion is signaled. * @return - * a {@link Disposable} that allows cancelling an asynchronous sequence + * a {@link Disposable} that allows canceling an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null, or @@ -9150,6 +10568,7 @@ public final Disposable forEachWhile(Predicate<? super T> onNext, Consumer<? sup * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.NONE) @SchedulerSupport(SchedulerSupport.NONE) public final Disposable forEachWhile(final Predicate<? super T> onNext, final Consumer<? super Throwable> onError, @@ -9176,6 +10595,14 @@ public final Disposable forEachWhile(final Predicate<? super T> onNext, final Co * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -9216,6 +10643,13 @@ public final <K> Flowable<GroupedFlowable<K, T>> groupBy(Function<? super T, ? e * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -9259,6 +10693,14 @@ public final <K> Flowable<GroupedFlowable<K, T>> groupBy(Function<? super T, ? e * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -9304,6 +10746,14 @@ public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -9352,6 +10802,14 @@ public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} @@ -9382,6 +10840,7 @@ public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, * @see <a href="http://reactivex.io/documentation/operators/groupby.html">ReactiveX operators documentation: GroupBy</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, ? extends K> keySelector, @@ -9391,7 +10850,122 @@ public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); ObjectHelper.verifyPositive(bufferSize, "bufferSize"); - return RxJavaPlugins.onAssembly(new FlowableGroupBy<T, K, V>(this, keySelector, valueSelector, bufferSize, delayError)); + return RxJavaPlugins.onAssembly(new FlowableGroupBy<T, K, V>(this, keySelector, valueSelector, bufferSize, delayError, null)); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedFlowable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. The {@code evictingMapFactory} is used to create a map that will + * be used to hold the {@link GroupedFlowable}s by key. The evicting map created by this factory must + * notify the provided {@code Consumer<Object>} with the entry value (not the key!) when an entry in this + * map has been evicted. The next source emission will bring about the completion of the evicted + * {@link GroupedFlowable}s and the arrival of an item with the same key as a completed {@link GroupedFlowable} + * will prompt the creation and emission of a new {@link GroupedFlowable} with that key. + * + * <p>A use case for specifying an {@code evictingMapFactory} is where the source is infinite and fast and + * over time the number of keys grows enough to be a concern in terms of the memory footprint of the + * internal hash map containing the {@link GroupedFlowable}s. + * + * <p>The map created by an {@code evictingMapFactory} must be thread-safe. + * + * <p>An example of an {@code evictingMapFactory} using <a href="https://google.github.io/guava/releases/24.0-jre/api/docs/com/google/common/cache/CacheBuilder.html">CacheBuilder</a> from the Guava library is below: + * + * <pre><code> + * Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = + * notify -> + * CacheBuilder + * .newBuilder() + * .maximumSize(3) + * .removalListener(entry -> { + * try { + * // emit the value not the key! + * notify.accept(entry.getValue()); + * } catch (Exception e) { + * throw new RuntimeException(e); + * } + * }) + * .<Integer, Object> build() + * .asMap(); + * + * // Emit 1000 items but ensure that the + * // internal map never has more than 3 items in it + * Flowable + * .range(1, 1000) + * // note that number of keys is 10 + * .groupBy(x -> x % 10, x -> x, true, 16, evictingMapFactory) + * .flatMap(g -> g) + * .forEach(System.out::println); + * </code></pre> + * + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/groupBy.png" alt=""> + * <p> + * <em>Note:</em> A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedFlowable}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + * <p> + * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>Both the returned and its inner {@code GroupedFlowable}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code GroupedFlowable}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that <em>may</em> + * lead to {@code OutOfMemoryError}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code groupBy} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - beta + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param delayError + * if true, the exception from the current Flowable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @param bufferSize + * the hint for how many {@link GroupedFlowable}s and element in each {@link GroupedFlowable} should be buffered + * @param evictingMapFactory + * The factory used to create a map that will be used by the implementation to hold the + * {@link GroupedFlowable}s. The evicting map created by this factory must + * notify the provided {@code Consumer<Object>} with the entry value (not the key!) when + * an entry in this map has been evicted. The next source emission will bring about the + * completion of the evicted {@link GroupedFlowable}s. See example above. + * @param <K> + * the key type + * @param <V> + * the element type + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see <a href="http://reactivex.io/documentation/operators/groupby.html">ReactiveX operators documentation: GroupBy</a> + * + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, ? extends K> keySelector, + Function<? super T, ? extends V> valueSelector, + boolean delayError, int bufferSize, + Function<? super Consumer<Object>, ? extends Map<K, Object>> evictingMapFactory) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(evictingMapFactory, "evictingMapFactory is null"); + + return RxJavaPlugins.onAssembly(new FlowableGroupBy<T, K, V>(this, keySelector, valueSelector, bufferSize, delayError, evictingMapFactory)); } /** @@ -9429,6 +11003,7 @@ public final <K, V> Flowable<GroupedFlowable<K, V>> groupBy(Function<? super T, * @see <a href="http://reactivex.io/documentation/operators/join.html">ReactiveX operators documentation: Join</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <TRight, TLeftEnd, TRightEnd, R> Flowable<R> groupJoin( @@ -9550,6 +11125,7 @@ public final Single<Boolean> isEmpty() { * @see <a href="http://reactivex.io/documentation/operators/join.html">ReactiveX operators documentation: Join</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <TRight, TLeftEnd, TRightEnd, R> Flowable<R> join( @@ -9565,7 +11141,6 @@ public final <TRight, TLeftEnd, TRightEnd, R> Flowable<R> join( this, other, leftEnd, rightEnd, resultSelector)); } - /** * Returns a Maybe that emits the last item emitted by this Flowable or completes if * this Flowable is empty. @@ -9608,6 +11183,7 @@ public final Maybe<T> lastElement() { * @see <a href="http://reactivex.io/documentation/operators/last.html">ReactiveX operators documentation: Last</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> last(T defaultItem) { @@ -9639,36 +11215,153 @@ public final Single<T> lastOrError() { } /** - * <strong>This method requires advanced knowledge about building operators; please consider + * <strong>This method requires advanced knowledge about building operators, please consider * other standard composition methods first;</strong> - * Lifts a function to the current Publisher and returns a new Publisher that when subscribed to will pass - * the values of the current Publisher through the Operator function. + * Returns a {@code Flowable} which, when subscribed to, invokes the {@link FlowableOperator#apply(Subscriber) apply(Subscriber)} method + * of the provided {@link FlowableOperator} for each individual downstream {@link Subscriber} and allows the + * insertion of a custom operator by accessing the downstream's {@link Subscriber} during this subscription phase + * and providing a new {@code Subscriber}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + * <p> + * Generally, such a new {@code Subscriber} will wrap the downstream's {@code Subscriber} and forwards the + * {@code onNext}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code cancel} and {@code request} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. * <p> - * In other words, this allows chaining Subscribers together on a Publisher for acting on the values within - * the Publisher. - * <p> {@code - * Publisher.map(...).filter(...).take(5).lift(new OperatorA()).lift(new OperatorB(...)).subscribe() + * Example: + * <pre><code> + * // Step 1: Create the consumer type that will be returned by the FlowableOperator.apply(): + * + * public final class CustomSubscriber<T> implements FlowableSubscriber<T>, Subscription { + * + * // The downstream's Subscriber that will receive the onXXX events + * final Subscriber<? super String> downstream; + * + * // The connection to the upstream source that will call this class' onXXX methods + * Subscription upstream; + * + * // The constructor takes the downstream subscriber and usually any other parameters + * public CustomSubscriber(Subscriber<? super String> downstream) { + * this.downstream = downstream; + * } + * + * // In the subscription phase, the upstream sends a Subscription to this class + * // and subsequently this class has to send a Subscription to the downstream. + * // Note that relaying the upstream's Subscription instance directly is not allowed in RxJava + * @Override + * public void onSubscribe(Subscription s) { + * if (upstream != null) { + * s.cancel(); + * } else { + * upstream = s; + * downstream.onSubscribe(this); + * } + * } + * + * // The upstream calls this with the next item and the implementation's + * // responsibility is to emit an item to the downstream based on the intended + * // business logic, or if it can't do so for the particular item, + * // request more from the upstream + * @Override + * public void onNext(T item) { + * String str = item.toString(); + * if (str.length() < 2) { + * downstream.onNext(str); + * } else { + * upstream.request(1); + * } + * } + * + * // Some operators may handle the upstream's error while others + * // could just forward it to the downstream. + * @Override + * public void onError(Throwable throwable) { + * downstream.onError(throwable); + * } + * + * // When the upstream completes, usually the downstream should complete as well. + * @Override + * public void onComplete() { + * downstream.onComplete(); + * } + * + * // Some operators have to intercept the downstream's request calls to trigger + * // the emission of queued items while others can simply forward the request + * // amount as is. + * @Override + * public void request(long n) { + * upstream.request(n); + * } + * + * // Some operators may use their own resources which should be cleaned up if + * // the downstream cancels the flow before it completed. Operators without + * // resources can simply forward the cancellation to the upstream. + * // In some cases, a canceled flag may be set by this method so that other parts + * // of this class may detect the cancellation and stop sending events + * // to the downstream. + * @Override + * public void cancel() { + * upstream.cancel(); + * } * } + * + * // Step 2: Create a class that implements the FlowableOperator interface and + * // returns the custom consumer type from above in its apply() method. + * // Such class may define additional parameters to be submitted to + * // the custom consumer type. + * + * final class CustomOperator<T> implements FlowableOperator<String> { + * @Override + * public Subscriber<? super String> apply(Subscriber<? super T> upstream) { + * return new CustomSubscriber<T>(upstream); + * } + * } + * + * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it + * // or reusing an existing one. + * + * Flowable.range(5, 10) + * .lift(new CustomOperator<Integer>()) + * .test() + * .assertResult("5", "6", "7", "8", "9"); + * </code></pre> * <p> - * If the operator you are creating is designed to act on the individual items emitted by a source - * Publisher, use {@code lift}. If your operator is designed to transform the source Publisher as a whole - * (for instance, by applying a particular set of existing RxJava operators to it) use {@link #compose}. + * Creating custom operators can be complicated and it is recommended one consults the + * <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + * <p> + * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Flowable} + * class and creating a {@link FlowableTransformer} with it is recommended. + * <p> + * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code Subscriber} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Flowable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code Subscriber} that should immediately cancel the upstream's {@code Subscription} in its + * {@code onSubscribe} method. Again, using a {@code FlowableTransformer} and extending the {@code Flowable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The {@code Operator} instance provided is responsible to be backpressure-aware or - * document the fact that the consumer of the returned {@code Publisher} has to apply one of + * <dd>The {@code Subscriber} instance returned by the {@link FlowableOperator} is responsible to be + * backpressure-aware or document the fact that the consumer of the returned {@code Publisher} has to apply one of * the {@code onBackpressureXXX} operators.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}.</dd> + * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link FlowableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.</dd> * </dl> * * @param <R> the output value type - * @param lifter the Operator that implements the Publisher-operating function to be applied to the source - * Publisher - * @return a Flowable that is the result of applying the lifted Operator to the source Publisher - * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators">RxJava wiki: Implementing Your Own Operators</a> + * @param lifter the {@link FlowableOperator} that receives the downstream's {@code Subscriber} and should return + * a {@code Subscriber} with custom behavior to be used as the consumer for the current + * {@code Flowable}. + * @return the new Flowable instance + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> + * @see #compose(FlowableTransformer) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> lift(FlowableOperator<? extends R, ? super T> lifter) { @@ -9676,6 +11369,52 @@ public final <R> Flowable<R> lift(FlowableOperator<? extends R, ? super T> lifte return RxJavaPlugins.onAssembly(new FlowableLift<R, T>(this, lifter)); } + /** + * Limits both the number of upstream items (after which the sequence completes) + * and the total downstream request amount requested from the upstream to + * possibly prevent the creation of excess items by the upstream. + * <p> + * The operator requests at most the given {@code count} of items from upstream even + * if the downstream requests more than that. For example, given a {@code limit(5)}, + * if the downstream requests 1, a request of 1 is submitted to the upstream + * and the operator remembers that only 4 items can be requested now on. A request + * of 5 at this point will request 4 from the upstream and any subsequent requests will + * be ignored. + * <p> + * Note that requests are negotiated on an operator boundary and {@code limit}'s amount + * may not be preserved further upstream. For example, + * {@code source.observeOn(Schedulers.computation()).limit(5)} will still request the + * default (128) elements from the given {@code source}. + * <p> + * The main use of this operator is with sources that are async boundaries that + * don't interfere with request amounts, such as certain {@code Flowable}-based + * network endpoints that relay downstream request amounts unchanged and are, therefore, + * prone to trigger excessive item creation/transmission over the network. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator requests a total of the given {@code count} items from the upstream.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code limit} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.6 - experimental + * @param count the maximum number of items and the total request amount, non-negative. + * Zero will immediately cancel the upstream on subscription and complete + * the downstream. + * @return the new Flowable instance + * @see #take(long) + * @see #rebatchRequests(int) + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final Flowable<T> limit(long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new FlowableLimit<T>(this, count)); + } + /** * Returns a Flowable that applies a specified function to each item emitted by the source Publisher and * emits the results of these function applications. @@ -9697,6 +11436,7 @@ public final <R> Flowable<R> lift(FlowableOperator<? extends R, ? super T> lifte * @see <a href="http://reactivex.io/documentation/operators/map.html">ReactiveX operators documentation: Map</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> map(Function<? super T, ? extends R> mapper) { @@ -9720,6 +11460,7 @@ public final <R> Flowable<R> map(Function<? super T, ? extends R> mapper) { * @return a Flowable that emits items that are the result of materializing the items and notifications * of the source Publisher * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Materialize</a> + * @see #dematerialize(Function) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -9749,6 +11490,7 @@ public final Flowable<Notification<T>> materialize() { * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> mergeWith(Publisher<? extends T> other) { @@ -9756,6 +11498,89 @@ public final Flowable<T> mergeWith(Publisher<? extends T> other) { return merge(this, other); } + /** + * Merges the sequence of items of this Flowable with the success value of the other SingleSource. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <p> + * The success value of the other {@code SingleSource} can get interleaved at any point of this + * {@code Flowable} sequence. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream and ensures the success item from the + * {@code SingleSource} is emitted only when there is a downstream demand.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code SingleSource} whose success value to merge with + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> mergeWith(@NonNull SingleSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithSingle<T>(this, other)); + } + + /** + * Merges the sequence of items of this Flowable with the success value of the other MaybeSource + * or waits for both to complete normally if the MaybeSource is empty. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <p> + * The success value of the other {@code MaybeSource} can get interleaved at any point of this + * {@code Flowable} sequence. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream and ensures the success item from the + * {@code MaybeSource} is emitted only when there is a downstream demand.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code MaybeSource} which provides a success value to merge with or completes + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> mergeWith(@NonNull MaybeSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithMaybe<T>(this, other)); + } + + /** + * Relays the items of this Flowable and completes only when the other CompletableSource completes + * as well. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to await for completion + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable<T> mergeWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithCompletable<T>(this, other)); + } + /** * Modifies a Publisher to perform its emissions and notifications on a specified {@link Scheduler}, * asynchronously with a bounded buffer of {@link #bufferSize()} slots. @@ -9764,6 +11589,11 @@ public final Flowable<T> mergeWith(Publisher<? extends T> other) { * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -9772,7 +11602,7 @@ public final Flowable<T> mergeWith(Publisher<? extends T> other) { * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any * of the {@code onBackpressureXXX} operators <strong>before</strong> applying {@code observeOn} itself.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -9784,6 +11614,7 @@ public final Flowable<T> mergeWith(Publisher<? extends T> other) { * @see #subscribeOn * @see #observeOn(Scheduler, boolean) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -9797,6 +11628,11 @@ public final Flowable<T> observeOn(Scheduler scheduler) { * asynchronously with a bounded buffer and optionally delays onError notifications. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -9805,7 +11641,7 @@ public final Flowable<T> observeOn(Scheduler scheduler) { * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any * of the {@code onBackpressureXXX} operators <strong>before</strong> applying {@code observeOn} itself.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -9821,6 +11657,7 @@ public final Flowable<T> observeOn(Scheduler scheduler) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -9834,6 +11671,11 @@ public final Flowable<T> observeOn(Scheduler scheduler, boolean delayError) { * asynchronously with a bounded buffer of configurable size and optionally delays onError notifications. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -9842,7 +11684,7 @@ public final Flowable<T> observeOn(Scheduler scheduler, boolean delayError) { * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any * of the {@code onBackpressureXXX} operators <strong>before</strong> applying {@code observeOn} itself.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -9859,8 +11701,10 @@ public final Flowable<T> observeOn(Scheduler scheduler, boolean delayError) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) { @@ -9888,6 +11732,7 @@ public final Flowable<T> observeOn(Scheduler scheduler, boolean delayError, int * @see <a href="http://reactivex.io/documentation/operators/filter.html">ReactiveX operators documentation: Filter</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> ofType(final Class<U> clazz) { @@ -9932,7 +11777,7 @@ public final Flowable<T> onBackpressureBuffer() { * </dl> * @param delayError * if true, an exception from the current Flowable is delayed until all buffered elements have been - * consumed by the downstream; if false, an exception is immediately signalled to the downstream, skipping + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping * any buffered element * @return the source Publisher modified to buffer items to the extent system resources allow * @see <a href="http://reactivex.io/documentation/operators/backpressure.html">ReactiveX operators documentation: backpressure operators</a> @@ -9946,9 +11791,9 @@ public final Flowable<T> onBackpressureBuffer(boolean delayError) { /** * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to - * a given amount of items until they can be emitted. The resulting Publisher will {@code onError} emitting - * a {@code BufferOverflowException} as soon as the buffer's capacity is exceeded, dropping all undelivered - * items, and cancelling the source. + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. * <p> * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png" alt=""> * <dl> @@ -9973,9 +11818,9 @@ public final Flowable<T> onBackpressureBuffer(int capacity) { /** * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to - * a given amount of items until they can be emitted. The resulting Publisher will {@code onError} emitting - * a {@code BufferOverflowException} as soon as the buffer's capacity is exceeded, dropping all undelivered - * items, and cancelling the source. + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. * <p> * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png" alt=""> * <dl> @@ -9989,7 +11834,7 @@ public final Flowable<T> onBackpressureBuffer(int capacity) { * @param capacity number of slots available in the buffer. * @param delayError * if true, an exception from the current Flowable is delayed until all buffered elements have been - * consumed by the downstream; if false, an exception is immediately signalled to the downstream, skipping + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping * any buffered element * @return the source {@code Publisher} modified to buffer items up to the given capacity. * @see <a href="http://reactivex.io/documentation/operators/backpressure.html">ReactiveX operators documentation: backpressure operators</a> @@ -10004,9 +11849,9 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError) /** * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to - * a given amount of items until they can be emitted. The resulting Publisher will {@code onError} emitting - * a {@code BufferOverflowException} as soon as the buffer's capacity is exceeded, dropping all undelivered - * items, and cancelling the source. + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. * <p> * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png" alt=""> * <dl> @@ -10020,7 +11865,7 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError) * @param capacity number of slots available in the buffer. * @param delayError * if true, an exception from the current Flowable is delayed until all buffered elements have been - * consumed by the downstream; if false, an exception is immediately signalled to the downstream, skipping + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping * any buffered element * @param unbounded * if true, the capacity value is interpreted as the internal "island" size of the unbounded buffer @@ -10032,15 +11877,15 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError) @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded) { - ObjectHelper.verifyPositive(capacity, "bufferSize"); + ObjectHelper.verifyPositive(capacity, "capacity"); return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<T>(this, capacity, unbounded, delayError, Functions.EMPTY_ACTION)); } /** * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to - * a given amount of items until they can be emitted. The resulting Publisher will {@code onError} emitting - * a {@code BufferOverflowException} as soon as the buffer's capacity is exceeded, dropping all undelivered - * items, cancelling the source, and notifying the producer with {@code onOverflow}. + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, canceling the source, and notifying the producer with {@code onOverflow}. * <p> * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png" alt=""> * <dl> @@ -10054,7 +11899,7 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError, * @param capacity number of slots available in the buffer. * @param delayError * if true, an exception from the current Flowable is delayed until all buffered elements have been - * consumed by the downstream; if false, an exception is immediately signalled to the downstream, skipping + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping * any buffered element * @param unbounded * if true, the capacity value is interpreted as the internal "island" size of the unbounded buffer @@ -10064,6 +11909,7 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError, * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded, @@ -10075,9 +11921,9 @@ public final Flowable<T> onBackpressureBuffer(int capacity, boolean delayError, /** * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to - * a given amount of items until they can be emitted. The resulting Publisher will {@code onError} emitting - * a {@code BufferOverflowException} as soon as the buffer's capacity is exceeded, dropping all undelivered - * items, cancelling the source, and notifying the producer with {@code onOverflow}. + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, canceling the source, and notifying the producer with {@code onOverflow}. * <p> * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png" alt=""> * <dl> @@ -10107,11 +11953,11 @@ public final Flowable<T> onBackpressureBuffer(int capacity, Action onOverflow) { * by {@code overflowStrategy} if the buffer capacity is exceeded. * * <ul> - * <li>{@code BackpressureOverflow.Strategy.ON_OVERFLOW_ERROR} (default) will {@code onError} dropping all undelivered items, - * cancelling the source, and notifying the producer with {@code onOverflow}. </li> + * <li>{@code BackpressureOverflow.Strategy.ON_OVERFLOW_ERROR} (default) will call {@code onError} dropping all undelivered items, + * canceling the source, and notifying the producer with {@code onOverflow}. </li> * <li>{@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_LATEST} will drop any new items emitted by the producer while - * the buffer is full, without generating any {@code onError}. Each drop will however invoke {@code onOverflow} - * to signal the overflow to the producer.</li>j + * the buffer is full, without generating any {@code onError}. Each drop will, however, invoke {@code onOverflow} + * to signal the overflow to the producer.</li> * <li>{@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_OLDEST} will drop the oldest items in the buffer in order to make * room for newly emitted ones. Overflow will not generate an{@code onError}, but each drop will invoke * {@code onOverflow} to signal the overflow to the producer.</li> @@ -10135,10 +11981,11 @@ public final Flowable<T> onBackpressureBuffer(int capacity, Action onOverflow) { * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onBackpressureBuffer(long capacity, Action onOverflow, BackpressureOverflowStrategy overflowStrategy) { - ObjectHelper.requireNonNull(overflowStrategy, "strategy is null"); + ObjectHelper.requireNonNull(overflowStrategy, "overflowStrategy is null"); ObjectHelper.verifyPositive(capacity, "capacity"); return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy<T>(this, capacity, onOverflow, overflowStrategy)); } @@ -10191,6 +12038,7 @@ public final Flowable<T> onBackpressureDrop() { * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onBackpressureDrop(Consumer<? super T> onDrop) { @@ -10212,7 +12060,6 @@ public final Flowable<T> onBackpressureDrop(Consumer<? super T> onDrop) { * <p> * Note that due to the nature of how backpressure requests are propagated through subscribeOn/observeOn, * requesting more than 1 from downstream doesn't guarantee a continuous delivery of onNext events. - * <p> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded @@ -10255,7 +12102,7 @@ public final Flowable<T> onBackpressureLatest() { * are expected to honor backpressure as well. * If any of them violate this expectation, the operator <em>may</em> throw an * {@code IllegalStateException} when the source {@code Publisher} completes or - * a {@code MissingBackpressureException} is signalled somewhere downstream.</dd> + * a {@code MissingBackpressureException} is signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -10267,6 +12114,7 @@ public final Flowable<T> onBackpressureLatest() { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onErrorResumeNext(Function<? super Throwable, ? extends Publisher<? extends T>> resumeFunction) { @@ -10298,7 +12146,7 @@ public final Flowable<T> onErrorResumeNext(Function<? super Throwable, ? extends * are expected to honor backpressure as well. * If any of them violate this expectation, the operator <em>may</em> throw an * {@code IllegalStateException} when the source {@code Publisher} completes or - * {@code MissingBackpressureException} is signalled somewhere downstream.</dd> + * {@code MissingBackpressureException} is signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -10310,6 +12158,7 @@ public final Flowable<T> onErrorResumeNext(Function<? super Throwable, ? extends * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onErrorResumeNext(final Publisher<? extends T> next) { @@ -10337,7 +12186,7 @@ public final Flowable<T> onErrorResumeNext(final Publisher<? extends T> next) { * <dd>The operator honors backpressure from downstream. The source {@code Publisher}s is expected to honor * backpressure as well. If it this expectation is violated, the operator <em>may</em> throw * {@code IllegalStateException} when the source {@code Publisher} completes or - * {@code MissingBackpressureException} is signalled somewhere downstream.</dd> + * {@code MissingBackpressureException} is signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -10349,6 +12198,7 @@ public final Flowable<T> onErrorResumeNext(final Publisher<? extends T> next) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onErrorReturn(Function<? super Throwable, ? extends T> valueSupplier) { @@ -10376,7 +12226,7 @@ public final Flowable<T> onErrorReturn(Function<? super Throwable, ? extends T> * <dd>The operator honors backpressure from downstream. The source {@code Publisher}s is expected to honor * backpressure as well. If it this expectation is violated, the operator <em>may</em> throw * {@code IllegalStateException} when the source {@code Publisher} completes or - * {@code MissingBackpressureException} is signalled somewhere downstream.</dd> + * {@code MissingBackpressureException} is signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -10388,6 +12238,7 @@ public final Flowable<T> onErrorReturn(Function<? super Throwable, ? extends T> * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onErrorReturnItem(final T item) { @@ -10422,7 +12273,7 @@ public final Flowable<T> onErrorReturnItem(final T item) { * are expected to honor backpressure as well. * If any of them violate this expectation, the operator <em>may</em> throw an * {@code IllegalStateException} when the source {@code Publisher} completes or - * {@code MissingBackpressureException} is signalled somewhere downstream.</dd> + * {@code MissingBackpressureException} is signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code onExceptionResumeNext} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -10434,6 +12285,7 @@ public final Flowable<T> onErrorReturnItem(final T item) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> onExceptionResumeNext(final Publisher<? extends T> next) { @@ -10451,7 +12303,7 @@ public final Flowable<T> onExceptionResumeNext(final Publisher<? extends T> next * <dt><b>Scheduler:</b></dt> * <dd>{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @return a Flowable which out references to the upstream producer and downstream Subscriber if + * @return a Flowable which nulls out references to the upstream producer and downstream Subscriber if * the sequence is terminated or downstream cancels * @since 2.0 */ @@ -10480,14 +12332,13 @@ public final Flowable<T> onTerminateDetach() { * <dt><b>Scheduler:</b></dt> * <dd>{@code parallel} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * <p>History: 2.0.5 - experimental + * <p>History: 2.0.5 - experimental; 2.1 - beta * @return the new ParallelFlowable instance - * @since 2.1 - beta + * @since 2.2 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue - @Beta public final ParallelFlowable<T> parallel() { return ParallelFlowable.from(this); } @@ -10510,15 +12361,14 @@ public final ParallelFlowable<T> parallel() { * <dt><b>Scheduler:</b></dt> * <dd>{@code parallel} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * <p>History: 2.0.5 - experimental + * <p>History: 2.0.5 - experimental; 2.1 - beta * @param parallelism the number of 'rails' to use * @return the new ParallelFlowable instance - * @since 2.1 - beta + * @since 2.2 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue - @Beta public final ParallelFlowable<T> parallel(int parallelism) { ObjectHelper.verifyPositive(parallelism, "parallelism"); return ParallelFlowable.from(this, parallelism); @@ -10543,16 +12393,15 @@ public final ParallelFlowable<T> parallel(int parallelism) { * <dt><b>Scheduler:</b></dt> * <dd>{@code parallel} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * <p>History: 2.0.5 - experimental + * <p>History: 2.0.5 - experimental; 2.1 - beta * @param parallelism the number of 'rails' to use * @param prefetch the number of items each 'rail' should prefetch * @return the new ParallelFlowable instance - * @since 2.1 - beta + * @since 2.2 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue - @Beta public final ParallelFlowable<T> parallel(int parallelism, int prefetch) { ObjectHelper.verifyPositive(parallelism, "parallelism"); ObjectHelper.verifyPositive(prefetch, "prefetch"); @@ -10595,7 +12444,7 @@ public final ConnectableFlowable<T> publish() { * <dd>The operator expects the source {@code Publisher} to honor backpressure and if this expectation is * violated, the operator will signal a {@code MissingBackpressureException} through the {@code Publisher} * provided to the function. Since the {@code Publisher} returned by the {@code selector} may be - * independent from the provided {@code Publisher} to the function, the output's backpressure behavior + * independent of the provided {@code Publisher} to the function, the output's backpressure behavior * is determined by this returned {@code Publisher}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code publish} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10627,7 +12476,7 @@ public final <R> Flowable<R> publish(Function<? super Flowable<T>, ? extends Pub * <dd>The operator expects the source {@code Publisher} to honor backpressure and if this expectation is * violated, the operator will signal a {@code MissingBackpressureException} through the {@code Publisher} * provided to the function. Since the {@code Publisher} returned by the {@code selector} may be - * independent from the provided {@code Publisher} to the function, the output's backpressure behavior + * independent of the provided {@code Publisher} to the function, the output's backpressure behavior * is determined by this returned {@code Publisher}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code publish} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10645,6 +12494,7 @@ public final <R> Flowable<R> publish(Function<? super Flowable<T>, ? extends Pub * @see <a href="http://reactivex.io/documentation/operators/publish.html">ReactiveX operators documentation: Publish</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> publish(Function<? super Flowable<T>, ? extends Publisher<? extends R>> selector, int prefetch) { @@ -10710,16 +12560,18 @@ public final Flowable<T> rebatchRequests(int n) { /** * Returns a Maybe that applies a specified accumulator function to the first item emitted by a source * Publisher, then feeds the result of that function along with the second item emitted by the source - * Publisher into the same function, and so on until all items have been emitted by the source Publisher, + * Publisher into the same function, and so on until all items have been emitted by the finite source Publisher, * and emits the final result from the final call to your function as its sole item. * <p> - * If the source is empty, a {@code NoSuchElementException} is signalled. - * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduce.png" alt=""> * <p> * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure of its downstream consumer and consumes the @@ -10737,6 +12589,7 @@ public final Flowable<T> rebatchRequests(int n) { * @see <a href="http://en.wikipedia.org/wiki/Fold_(higher-order_function)">Wikipedia: Fold (higher-order function)</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { @@ -10748,7 +12601,7 @@ public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { * Returns a Single that applies a specified accumulator function to the first item emitted by a source * Publisher and a specified seed value, then feeds the result of that function along with the second item * emitted by a Publisher into the same function, and so on until all items have been emitted by the - * source Publisher, emitting the final result from the final call to your function as its sole item. + * finite source Publisher, emitting the final result from the final call to your function as its sole item. * <p> * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceSeed.png" alt=""> * <p> @@ -10773,6 +12626,10 @@ public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { * * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item))); * </code></pre> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure of its downstream consumer and consumes the @@ -10794,6 +12651,7 @@ public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { * @see #reduceWith(Callable, BiFunction) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> reduce(R seed, BiFunction<R, ? super T, R> reducer) { @@ -10806,7 +12664,7 @@ public final <R> Single<R> reduce(R seed, BiFunction<R, ? super T, R> reducer) { * Returns a Single that applies a specified accumulator function to the first item emitted by a source * Publisher and a seed value derived from calling a specified seedSupplier, then feeds the result * of that function along with the second item emitted by a Publisher into the same function, and so on until - * all items have been emitted by the source Publisher, emitting the final result from the final call to your + * all items have been emitted by the finite source Publisher, emitting the final result from the final call to your * function as its sole item. * <p> * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceSeed.png" alt=""> @@ -10814,6 +12672,10 @@ public final <R> Single<R> reduce(R seed, BiFunction<R, ? super T, R> reducer) { * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure of its downstream consumer and consumes the @@ -10834,6 +12696,7 @@ public final <R> Single<R> reduce(R seed, BiFunction<R, ? super T, R> reducer) { * @see <a href="http://en.wikipedia.org/wiki/Fold_(higher-order_function)">Wikipedia: Fold (higher-order function)</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> reduceWith(Callable<R> seedSupplier, BiFunction<R, ? super T, R> reducer) { @@ -10921,6 +12784,7 @@ public final Flowable<T> repeat(long times) { * @see <a href="http://reactivex.io/documentation/operators/repeat.html">ReactiveX operators documentation: Repeat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> repeatUntil(BooleanSupplier stop) { @@ -10951,6 +12815,7 @@ public final Flowable<T> repeatUntil(BooleanSupplier stop) { * @see <a href="http://reactivex.io/documentation/operators/repeat.html">ReactiveX operators documentation: Repeat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> repeatWhen(final Function<? super Flowable<Object>, ? extends Publisher<?>> handler) { @@ -11009,6 +12874,7 @@ public final ConnectableFlowable<T> replay() { * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publisher<R>> selector) { @@ -11021,6 +12887,9 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, * replaying {@code bufferSize} notifications. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11044,6 +12913,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publisher<R>> selector, final int bufferSize) { @@ -11057,6 +12927,9 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, * replaying no more than {@code bufferSize} items that were emitted within a specified time window. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fnt.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11096,6 +12969,9 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, * replaying no more than {@code bufferSize} items that were emitted within a specified time window. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fnts.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11103,7 +12979,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -11128,6 +13004,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publisher<R>> selector, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { @@ -11144,6 +13021,9 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, * replaying a maximum of {@code bufferSize} items. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fns.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11151,7 +13031,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -11169,6 +13049,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final <R> Flowable<R> replay(final Function<? super Flowable<T>, ? extends Publisher<R>> selector, final int bufferSize, final Scheduler scheduler) { @@ -11228,7 +13109,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -11248,6 +13129,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publisher<R>> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { @@ -11268,7 +13150,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -11284,6 +13166,7 @@ public final <R> Flowable<R> replay(Function<? super Flowable<T>, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/replay.html">ReactiveX operators documentation: Replay</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final <R> Flowable<R> replay(final Function<? super Flowable<T>, ? extends Publisher<R>> selector, final Scheduler scheduler) { @@ -11299,6 +13182,9 @@ public final <R> Flowable<R> replay(final Function<? super Flowable<T>, ? extend * an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, but only * when its {@code connect} method is called. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.n.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11329,6 +13215,9 @@ public final ConnectableFlowable<T> replay(final int bufferSize) { * Publisher resembles an ordinary Publisher, except that it does not begin emitting items when it is * subscribed to, but only when its {@code connect} method is called. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.nt.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11363,6 +13252,9 @@ public final ConnectableFlowable<T> replay(int bufferSize, long time, TimeUnit u * Connectable Publisher resembles an ordinary Publisher, except that it does not begin emitting items * when it is subscribed to, but only when its {@code connect} method is called. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.nts.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11370,7 +13262,7 @@ public final ConnectableFlowable<T> replay(int bufferSize, long time, TimeUnit u * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param bufferSize @@ -11405,6 +13297,9 @@ public final ConnectableFlowable<T> replay(final int bufferSize, final long time * an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, but only * when its {@code connect} method is called. * <p> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.ns.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -11412,7 +13307,7 @@ public final ConnectableFlowable<T> replay(final int bufferSize, final long time * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param bufferSize @@ -11475,7 +13370,7 @@ public final ConnectableFlowable<T> replay(long time, TimeUnit unit) { * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -11510,7 +13405,7 @@ public final ConnectableFlowable<T> replay(final long time, final TimeUnit unit, * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will * request 100 elements from the underlying Publisher sequence.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -11580,6 +13475,7 @@ public final Flowable<T> retry() { * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> retry(BiPredicate<? super Integer, ? super Throwable> predicate) { @@ -11611,7 +13507,7 @@ public final Flowable<T> retry(BiPredicate<? super Integer, ? super Throwable> p * </dl> * * @param count - * number of retry attempts before failing + * the number of times to resubscribe if the current Flowable fails * @return the source Publisher modified with retry logic * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @@ -11632,11 +13528,12 @@ public final Flowable<T> retry(long count) { * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Flowable fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> retry(long times, Predicate<? super Throwable> predicate) { @@ -11681,6 +13578,7 @@ public final Flowable<T> retry(Predicate<? super Throwable> predicate) { * @return the new Flowable instance */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> retryUntil(final BooleanSupplier stop) { @@ -11697,19 +13595,19 @@ public final Flowable<T> retryUntil(final BooleanSupplier stop) { * resubscribe to the source Publisher. * <p> * <img width="640" height="430" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retryWhen.f.png" alt=""> - * + * <p> * Example: * * This retries 3 times, each time incrementing the number of seconds it waits. * * <pre><code> - * Publisher.create((Subscriber<? super String> s) -> { + * Flowable.create((FlowableEmitter<? super String> s) -> { * System.out.println("subscribing"); * s.onError(new RuntimeException("always fails")); - * }).retryWhen(attempts -> { + * }, BackpressureStrategy.BUFFER).retryWhen(attempts -> { * return attempts.zipWith(Flowable.range(1, 3), (n, i) -> i).flatMap(i -> { * System.out.println("delay retry by " + i + " second(s)"); - * return Publisher.timer(i, TimeUnit.SECONDS); + * return Flowable.timer(i, TimeUnit.SECONDS); * }); * }).blockingForEach(System.out::println); * </code></pre> @@ -11725,9 +13623,35 @@ public final Flowable<T> retryUntil(final BooleanSupplier stop) { * delay retry by 3 second(s) * subscribing * } </pre> + * <p> + * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signaling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + * <p> + * The following example demonstrates how to retry an asynchronous source with a delay: + * <pre><code> + * Flowable.timer(1, TimeUnit.SECONDS) + * .doOnSubscribe(s -> System.out.println("subscribing")) + * .map(v -> { throw new RuntimeException(); }) + * .retryWhen(errors -> { + * AtomicInteger counter = new AtomicInteger(); + * return errors + * .takeWhile(e -> counter.getAndIncrement() != 3) + * .flatMap(e -> { + * System.out.println("delay retry by " + counter.get() + " second(s)"); + * return Flowable.timer(counter.get(), TimeUnit.SECONDS); + * }); + * }) + * .blockingSubscribe(System.out::println, System.out::println); + * </code></pre> * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * <dd>The operator honors downstream backpressure and expects both the source + * and inner {@code Publisher}s to honor backpressure as well. * If this expectation is violated, the operator <em>may</em> throw an {@code IllegalStateException}.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -11740,6 +13664,7 @@ public final Flowable<T> retryUntil(final BooleanSupplier stop) { * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> retryWhen( @@ -11753,7 +13678,7 @@ public final Flowable<T> retryWhen( * Subscribes to the current Flowable and wraps the given Subscriber into a SafeSubscriber * (if not already a SafeSubscriber) that * deals with exceptions thrown by a misbehaving Subscriber (that doesn't follow the - * Reactive-Streams specification). + * Reactive Streams specification). * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator leaves the reactive world and the backpressure behavior depends on the Subscriber's behavior.</dd> @@ -11847,7 +13772,7 @@ public final Flowable<T> sample(long period, TimeUnit unit, boolean emitLast) { * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param period @@ -11863,6 +13788,7 @@ public final Flowable<T> sample(long period, TimeUnit unit, boolean emitLast) { * @see #throttleLast(long, TimeUnit, Scheduler) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> sample(long period, TimeUnit unit, Scheduler scheduler) { @@ -11881,7 +13807,7 @@ public final Flowable<T> sample(long period, TimeUnit unit, Scheduler scheduler) * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * <p>History: 2.0.5 - experimental @@ -11903,6 +13829,7 @@ public final Flowable<T> sample(long period, TimeUnit unit, Scheduler scheduler) * @since 2.1 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> sample(long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { @@ -11934,6 +13861,7 @@ public final Flowable<T> sample(long period, TimeUnit unit, Scheduler scheduler, * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Backpressure">RxJava wiki: Backpressure</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> sample(Publisher<U> sampler) { @@ -11971,6 +13899,7 @@ public final <U> Flowable<T> sample(Publisher<U> sampler) { * @since 2.1 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> sample(Publisher<U> sampler, boolean emitLast) { @@ -11990,7 +13919,7 @@ public final <U> Flowable<T> sample(Publisher<U> sampler, boolean emitLast) { * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. - * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signalled somewhere downstream.</dd> + * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code scan} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -12003,6 +13932,7 @@ public final <U> Flowable<T> sample(Publisher<U> sampler, boolean emitLast) { * @see <a href="http://reactivex.io/documentation/operators/scan.html">ReactiveX operators documentation: Scan</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> scan(BiFunction<T, T, T> accumulator) { @@ -12039,7 +13969,7 @@ public final Flowable<T> scan(BiFunction<T, T, T> accumulator) { * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. - * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signalled somewhere downstream.</dd> + * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code scan} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -12056,10 +13986,11 @@ public final Flowable<T> scan(BiFunction<T, T, T> accumulator) { * @see <a href="http://reactivex.io/documentation/operators/scan.html">ReactiveX operators documentation: Scan</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> scan(final R initialValue, BiFunction<R, ? super T, R> accumulator) { - ObjectHelper.requireNonNull(initialValue, "seed is null"); + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); return scanWith(Functions.justCallable(initialValue), accumulator); } @@ -12078,7 +14009,7 @@ public final <R> Flowable<R> scan(final R initialValue, BiFunction<R, ? super T, * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. - * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signalled somewhere downstream.</dd> + * Violating this expectation, a {@code MissingBackpressureException} <em>may</em> get signaled somewhere downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code scanWith} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -12095,6 +14026,7 @@ public final <R> Flowable<R> scan(final R initialValue, BiFunction<R, ? super T, * @see <a href="http://reactivex.io/documentation/operators/scan.html">ReactiveX operators documentation: Scan</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> scanWith(Callable<R> seedSupplier, BiFunction<R, ? super T, R> accumulator) { @@ -12134,16 +14066,16 @@ public final Flowable<T> serialize() { } /** - * Returns a new {@link Publisher} that multicasts (shares) the original {@link Publisher}. As long as + * Returns a new {@link Publisher} that multicasts (and shares a single subscription to) the original {@link Publisher}. As long as * there is at least one {@link Subscriber} this {@link Publisher} will be subscribed and emitting data. - * When all subscribers have cancelled it will cancel the source {@link Publisher}. + * When all subscribers have canceled it will cancel the source {@link Publisher}. * <p> - * This is an alias for {@link #publish()}.{@link ConnectableFlowable#refCount()}. + * This is an alias for {@link #publish()}.{@link ConnectableFlowable#refCount() refCount()}. * <p> * <img width="640" height="510" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishRefCount.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator honors backpressure and and expects the source {@code Publisher} to honor backpressure as well. + * <dd>The operator honors backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator will signal a {@code MissingBackpressureException} to * its {@code Subscriber}s.</dd> * <dt><b>Scheduler:</b></dt> @@ -12188,7 +14120,7 @@ public final Maybe<T> singleElement() { /** * Returns a Single that emits the single item emitted by the source Publisher, if that Publisher * emits only a single item, or a default item if the source Publisher emits no items. If the source - * Publisher emits more than one item, an {@code IllegalArgumentException} is signalled instead. + * Publisher emits more than one item, an {@code IllegalArgumentException} is signaled instead. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/singleOrDefault.png" alt=""> * <dl> @@ -12206,6 +14138,7 @@ public final Maybe<T> singleElement() { * @see <a href="http://reactivex.io/documentation/operators/first.html">ReactiveX operators documentation: First</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> single(T defaultItem) { @@ -12216,8 +14149,8 @@ public final Single<T> single(T defaultItem) { /** * Returns a Single that emits the single item emitted by this Flowable, if this Flowable * emits only a single item, otherwise - * if this Flowable completes without emitting any items a {@link NoSuchElementException} will be signalled and - * if this Flowable emits more than one item, an {@code IllegalArgumentException} will be signalled. + * if this Flowable completes without emitting any items a {@link NoSuchElementException} will be signaled and + * if this Flowable emits more than one item, an {@code IllegalArgumentException} will be signaled. * <p> * <img width="640" height="205" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/singleOrError.png" alt=""> * <dl> @@ -12274,7 +14207,7 @@ public final Flowable<T> skip(long count) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/skip.t.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code skip} does not operate on any particular scheduler but uses the current time @@ -12303,7 +14236,7 @@ public final Flowable<T> skip(long time, TimeUnit unit) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/skip.ts.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>You specify which {@link Scheduler} this operator will use for the timed skipping</dd> @@ -12373,7 +14306,7 @@ public final Flowable<T> skipLast(int count) { * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code skipLast} does not operate on any particular scheduler but uses the current time @@ -12404,7 +14337,7 @@ public final Flowable<T> skipLast(long time, TimeUnit unit) { * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code skipLast} does not operate on any particular scheduler but uses the current time @@ -12416,8 +14349,8 @@ public final Flowable<T> skipLast(long time, TimeUnit unit) { * @param unit * the time unit of {@code time} * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @return a Flowable that drops those items emitted by the source Publisher in a time window before the * source completes defined by {@code time} * @see <a href="http://reactivex.io/documentation/operators/skiplast.html">ReactiveX operators documentation: SkipLast</a> @@ -12438,7 +14371,7 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, boolean delayError) * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>You specify which {@link Scheduler} this operator will use for tracking the current time</dd> @@ -12470,7 +14403,7 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler) * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> * <dd>You specify which {@link Scheduler} this operator will use to track the current time</dd> @@ -12483,8 +14416,8 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler) * @param scheduler * the scheduler used as the time source * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @return a Flowable that drops those items emitted by the source Publisher in a time window before the * source completes defined by {@code time} and {@code scheduler} * @see <a href="http://reactivex.io/documentation/operators/skiplast.html">ReactiveX operators documentation: SkipLast</a> @@ -12505,10 +14438,10 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler, * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Backpressure:</b></dt> - * <dd>The operator doesn't support backpressure as it uses time to skip arbitrary number of elements and + * <dd>The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -12518,8 +14451,8 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler, * @param scheduler * the scheduler used as the time source * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @param bufferSize * the hint about how many elements to expect to be skipped * @return a Flowable that drops those items emitted by the source Publisher in a time window before the @@ -12527,6 +14460,7 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler, * @see <a href="http://reactivex.io/documentation/operators/skiplast.html">ReactiveX operators documentation: SkipLast</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { @@ -12560,6 +14494,7 @@ public final Flowable<T> skipLast(long time, TimeUnit unit, Scheduler scheduler, * @see <a href="http://reactivex.io/documentation/operators/skipuntil.html">ReactiveX operators documentation: SkipUntil</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> skipUntil(Publisher<U> other) { @@ -12587,6 +14522,7 @@ public final <U> Flowable<T> skipUntil(Publisher<U> other) { * @see <a href="http://reactivex.io/documentation/operators/skipwhile.html">ReactiveX operators documentation: SkipWhile</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> skipWhile(Predicate<? super T> predicate) { @@ -12642,6 +14578,7 @@ public final Flowable<T> sorted() { * @return a Flowable that emits the items emitted by the source Publisher in sorted order */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> sorted(Comparator<? super T> sortFunction) { @@ -12699,6 +14636,7 @@ public final Flowable<T> startWith(Iterable<? extends T> items) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> startWith(Publisher<? extends T> other) { @@ -12728,10 +14666,11 @@ public final Flowable<T> startWith(Publisher<? extends T> other) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> startWith(T value) { - ObjectHelper.requireNonNull(value, "item is null"); + ObjectHelper.requireNonNull(value, "value is null"); return concatArray(just(value), this); } @@ -12913,10 +14852,12 @@ public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super T * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null, or - * if {@code onComplete} is null + * if {@code onComplete} is null, or + * if {@code onSubscribe} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, @@ -12960,7 +14901,7 @@ public final void subscribe(Subscriber<? super T> s) { * If the {@link Flowable} rejects the subscription attempt or otherwise fails it will signal * the error via {@link FlowableSubscriber#onError(Throwable)}. * <p> - * This subscribe method relaxes the following Reactive-Streams rules: + * This subscribe method relaxes the following Reactive Streams rules: * <ul> * <li>§1.3: onNext should not be called concurrently until onSubscribe returns. * <b>FlowableSubscriber.onSubscribe should make sure a sync or async call triggered by request() is safe.</b></li> @@ -12978,19 +14919,18 @@ public final void subscribe(Subscriber<? super T> s) { * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * <p>History: 2.0.7 - experimental + * <p>History: 2.0.7 - experimental; 2.1 - beta * @param s the FlowableSubscriber that will consume signals from this Flowable - * @since 2.1 - beta + * @since 2.2 */ @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) - @Beta public final void subscribe(FlowableSubscriber<? super T> s) { ObjectHelper.requireNonNull(s, "s is null"); try { Subscriber<? super T> z = RxJavaPlugins.onSubscribe(this, s); - ObjectHelper.requireNonNull(z, "Plugin returned null Subscriber"); + ObjectHelper.requireNonNull(z, "The RxJavaPlugins.onSubscribe hook returned a null FlowableSubscriber. Please check the handler provided to RxJavaPlugins.setOnFlowableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); subscribeActual(z); } catch (NullPointerException e) { // NOPMD @@ -13009,9 +14949,10 @@ public final void subscribe(FlowableSubscriber<? super T> s) { /** * Operator implementations (both source and intermediate) should implement this method that - * performs the necessary business logic. - * <p>There is no need to call any of the plugin hooks on the current Flowable instance or - * the Subscriber. + * performs the necessary business logic and handles the incoming {@link Subscriber}s. + * <p>There is no need to call any of the plugin hooks on the current {@code Flowable} instance or + * the {@code Subscriber}; all hooks and basic safeguards have been + * applied by {@link #subscribe(Subscriber)} before this method gets called. * @param s the incoming Subscriber, never null */ protected abstract void subscribeActual(Subscriber<? super T> s); @@ -13064,7 +15005,7 @@ public final <E extends Subscriber<? super T>> E subscribeWith(E subscriber) { * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure * behavior.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -13077,6 +15018,7 @@ public final <E extends Subscriber<? super T>> E subscribeWith(E subscriber) { * @see #subscribeOn(Scheduler, boolean) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler) { @@ -13098,9 +15040,9 @@ public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler) { * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure * behavior.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> - * + * <p>History: 2.1.1 - experimental * @param scheduler * the {@link Scheduler} to perform subscription actions on * @param requestOn if true, requests are rerouted to the given Scheduler as well (strong pipelining) @@ -13111,12 +15053,12 @@ public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/subscribeon.html">ReactiveX operators documentation: SubscribeOn</a> * @see <a href="http://www.grahamlea.com/2014/07/rxjava-threading-examples/">RxJava Threading Examples</a> * @see #observeOn - * @since 2.1.1 - experimental + * @since 2.2 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) - @Experimental public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); return RxJavaPlugins.onAssembly(new FlowableSubscribeOn<T>(this, scheduler, requestOn)); @@ -13125,14 +15067,14 @@ public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler, boolean reque /** * Returns a Flowable that emits the items emitted by the source Publisher or the items of an alternate * Publisher if the source Publisher is empty. + * <p> * <img width="640" height="255" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchifempty.png" alt=""> - * <p/> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>If the source {@code Publisher} is empty, the alternate {@code Publisher} is expected to honor backpressure. * If the source {@code Publisher} is non-empty, it is expected to honor backpressure as instead. * In either case, if violated, a {@code MissingBackpressureException} <em>may</em> get - * signalled somewhere downstream. + * signaled somewhere downstream. * </dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.</dd> @@ -13145,6 +15087,7 @@ public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler, boolean reque * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> switchIfEmpty(Publisher<? extends T> other) { @@ -13158,7 +15101,7 @@ public final Flowable<T> switchIfEmpty(Publisher<? extends T> other) { * of these Publishers. * <p> * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. - * If the upstream Publisher signals an onError, the inner Publisher is cancelled and the error delivered in-sequence. + * If the upstream Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. * <p> * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> * <dl> @@ -13173,10 +15116,11 @@ public final Flowable<T> switchIfEmpty(Publisher<? extends T> other) { * * @param <R> the element type of the inner Publishers and the output * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMapDelayError(Function) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -13191,7 +15135,7 @@ public final <R> Flowable<R> switchMap(Function<? super T, ? extends Publisher<? * of these Publishers. * <p> * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. - * If the upstream Publisher signals an onError, the inner Publisher is cancelled and the error delivered in-sequence. + * If the upstream Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. * <p> * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> * <dl> @@ -13206,12 +15150,13 @@ public final <R> Flowable<R> switchMap(Function<? super T, ? extends Publisher<? * * @param <R> the element type of the inner Publishers and the output * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param bufferSize * the number of elements to prefetch from the current active inner Publisher * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -13220,6 +15165,99 @@ public final <R> Flowable<R> switchMap(Function<? super T, ? extends Publisher<? return switchMap0(mapper, bufferSize, false); } + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + * <p> + * <img width="640" height="521" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapCompletable.f.png" alt=""> + * <p> + * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Flowable}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator consumes the current {@link Flowable} in an unbounded manner and otherwise + * does not have backpressure in its return type because no items are ever produced.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If either this {@code Flowable} or the active {@code CompletableSource} signals an {@code onError}, + * the resulting {@code Completable} is terminated immediately with that {@code Throwable}. + * Use the {@link #switchMapCompletableDelayError(Function)} to delay such inner failures until + * every inner {@code CompletableSource}s and the main {@code Flowable} terminates in some fashion. + * If they fail concurrently, the operator may combine the {@code Throwable}s into a + * {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signal it to the downstream instead. If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signaled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + * </dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletable(@NonNull Function<? super T, ? extends CompletableSource> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapCompletable<T>(this, mapper, false)); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running and delaying any main or inner errors until all + * of them terminate. + * <p> + * <img width="640" height="453" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapCompletableDelayError.f.png" alt=""> + * <p> + * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Flowable}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator consumes the current {@link Flowable} in an unbounded manner and otherwise + * does not have backpressure in its return type because no items are ever produced.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>Errors of this {@code Flowable} and all the {@code CompletableSource}s, who had the chance + * to run to their completion, are delayed until + * all of them terminate in some fashion. At this point, if there was only one failure, the respective + * {@code Throwable} is emitted to the downstream. If there was more than one failure, the + * operator combines all {@code Throwable}s into a {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signals that to the downstream. + * If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signaled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + * </dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletableDelayError(@NonNull Function<? super T, ? extends CompletableSource> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapCompletable<T>(this, mapper, true)); + } + /** * Returns a new Publisher by applying a function that you supply to each item emitted by the source * Publisher that returns a Publisher, and then emitting the items emitted by the most recently emitted @@ -13227,7 +15265,7 @@ public final <R> Flowable<R> switchMap(Function<? super T, ? extends Publisher<? * <p> * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. * If the upstream Publisher signals an onError, the termination of the last inner Publisher will emit that error as is - * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signalled. + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. * <p> * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> * <dl> @@ -13242,10 +15280,11 @@ public final <R> Flowable<R> switchMap(Function<? super T, ? extends Publisher<? * * @param <R> the element type of the inner Publishers and the output * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -13262,7 +15301,7 @@ public final <R> Flowable<R> switchMapDelayError(Function<? super T, ? extends P * <p> * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. * If the upstream Publisher signals an onError, the termination of the last inner Publisher will emit that error as is - * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signalled. + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. * <p> * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> * <dl> @@ -13277,12 +15316,13 @@ public final <R> Flowable<R> switchMapDelayError(Function<? super T, ? extends P * * @param <R> the element type of the inner Publishers and the output * @param mapper - * a function that, when applied to an item emitted by the source Publisher, returns an + * a function that, when applied to an item emitted by the source Publisher, returns a * Publisher * @param bufferSize * the number of elements to prefetch from the current active inner Publisher * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -13306,6 +15346,151 @@ <R> Flowable<R> switchMap0(Function<? super T, ? extends Publisher<? extends R>> return RxJavaPlugins.onAssembly(new FlowableSwitchMap<T, R>(this, mapper, bufferSize, delayError)); } + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available while failing immediately if this {@code Flowable} or any of the + * active inner {@code MaybeSource}s fail. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>This operator terminates with an {@code onError} if this {@code Flowable} or any of + * the inner {@code MaybeSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Flowable} or from any of + * the inner {@code MaybeSource}s will be forwarded to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapMaybe(Function) + * @see #switchMapMaybeDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> switchMapMaybe(@NonNull Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapMaybe<T, R>(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available, delaying errors from this {@code Flowable} or the inner {@code MaybeSource}s until all terminate. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapMaybe(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> switchMapMaybeDelayError(@NonNull Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapMaybe<T, R>(this, mapper, true)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one + * while failing immediately if this {@code Flowable} or any of the + * active inner {@code SingleSource}s fail. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>This operator terminates with an {@code onError} if this {@code Flowable} or any of + * the inner {@code SingleSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Flowable} or from any of + * the inner {@code SingleSource}s will be forwarded to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code SingleSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapSingleDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> switchMapSingle(@NonNull Function<? super T, ? extends SingleSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapSingle<T, R>(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one, + * delaying errors from this {@code Flowable} or the inner {@code SingleSource}s until all terminate. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code SingleSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapSingle(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Flowable<R> switchMapSingleDelayError(@NonNull Function<? super T, ? extends SingleSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapSingle<T, R>(this, mapper, true)); + } + /** * Returns a Flowable that emits only the first {@code count} items emitted by the source Publisher. If the source emits fewer than * {@code count} items then all of its items are emitted. @@ -13344,6 +15529,9 @@ public final Flowable<T> take(long count) { * Returns a Flowable that emits those items emitted by source Publisher before a specified time runs * out. * <p> + * If time runs out before the {@code Flowable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/take.t.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -13371,13 +15559,16 @@ public final Flowable<T> take(long time, TimeUnit unit) { * Returns a Flowable that emits those items emitted by source Publisher before a specified time (on a * specified Scheduler) runs out. * <p> + * If time runs out before the {@code Flowable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/take.ts.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure * behavior.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -13524,8 +15715,8 @@ public final Flowable<T> takeLast(long count, long time, TimeUnit unit, Schedule * @param scheduler * the {@link Scheduler} that provides the timestamps for the observed items * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @param bufferSize * the hint about how many elements to expect to be last * @return a Flowable that emits at most {@code count} items from the source Publisher that were emitted @@ -13536,6 +15727,7 @@ public final Flowable<T> takeLast(long count, long time, TimeUnit unit, Schedule * @see <a href="http://reactivex.io/documentation/operators/takelast.html">ReactiveX operators documentation: TakeLast</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> takeLast(long count, long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { @@ -13598,8 +15790,8 @@ public final Flowable<T> takeLast(long time, TimeUnit unit) { * @param unit * the time unit of {@code time} * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @return a Flowable that emits the items from the source Publisher that were emitted in the window of * time before the Publisher completed specified by {@code time} * @see <a href="http://reactivex.io/documentation/operators/takelast.html">ReactiveX operators documentation: TakeLast</a> @@ -13624,7 +15816,7 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, boolean delayError) * lead to {@code OutOfMemoryError} due to internal buffer bloat. * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -13658,7 +15850,7 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, Scheduler scheduler) * lead to {@code OutOfMemoryError} due to internal buffer bloat. * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -13668,8 +15860,8 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, Scheduler scheduler) * @param scheduler * the Scheduler that provides the timestamps for the Observed items * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @return a Flowable that emits the items from the source Publisher that were emitted in the window of * time before the Publisher completed specified by {@code time}, where the timing information is * provided by {@code scheduler} @@ -13695,7 +15887,7 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, Scheduler scheduler, * lead to {@code OutOfMemoryError} due to internal buffer bloat. * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -13705,8 +15897,8 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, Scheduler scheduler, * @param scheduler * the Scheduler that provides the timestamps for the Observed items * @param delayError - * if true, an exception signalled by the current Flowable is delayed until the regular elements are consumed - * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped * @param bufferSize * the hint about how many elements to expect to be last * @return a Flowable that emits the items from the source Publisher that were emitted in the window of @@ -13747,6 +15939,7 @@ public final Flowable<T> takeLast(long time, TimeUnit unit, Scheduler scheduler, * @since 1.1.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> takeUntil(Predicate<? super T> stopPredicate) { @@ -13776,6 +15969,7 @@ public final Flowable<T> takeUntil(Predicate<? super T> stopPredicate) { * @see <a href="http://reactivex.io/documentation/operators/takeuntil.html">ReactiveX operators documentation: TakeUntil</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<T> takeUntil(Publisher<U> other) { @@ -13804,6 +15998,7 @@ public final <U> Flowable<T> takeUntil(Publisher<U> other) { * @see Flowable#takeUntil(Predicate) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> takeWhile(Predicate<? super T> predicate) { @@ -13815,7 +16010,7 @@ public final Flowable<T> takeWhile(Predicate<? super T> predicate) { * Returns a Flowable that emits only the first item emitted by the source Publisher during sequential * time windows of a specified duration. * <p> - * This differs from {@link #throttleLast} in that this only tracks passage of time whereas + * This differs from {@link #throttleLast} in that this only tracks the passage of time whereas * {@link #throttleLast} ticks at scheduled intervals. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png" alt=""> @@ -13845,7 +16040,7 @@ public final Flowable<T> throttleFirst(long windowDuration, TimeUnit unit) { * Returns a Flowable that emits only the first item emitted by the source Publisher during sequential * time windows of a specified duration, where the windows are managed by a specified Scheduler. * <p> - * This differs from {@link #throttleLast} in that this only tracks passage of time whereas + * This differs from {@link #throttleLast} in that this only tracks the passage of time whereas * {@link #throttleLast} ticks at scheduled intervals. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.s.png" alt=""> @@ -13853,7 +16048,7 @@ public final Flowable<T> throttleFirst(long windowDuration, TimeUnit unit) { * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param skipDuration @@ -13868,6 +16063,7 @@ public final Flowable<T> throttleFirst(long windowDuration, TimeUnit unit) { * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Backpressure">RxJava wiki: Backpressure</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { @@ -13881,7 +16077,7 @@ public final Flowable<T> throttleFirst(long skipDuration, TimeUnit unit, Schedul * time windows of a specified duration. * <p> * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas - * {@link #throttleFirst} does not tick, it just tracks passage of time. + * {@link #throttleFirst} does not tick, it just tracks the passage of time. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLast.png" alt=""> * <dl> @@ -13913,14 +16109,14 @@ public final Flowable<T> throttleLast(long intervalDuration, TimeUnit unit) { * time windows of a specified duration, where the duration is governed by a specified Scheduler. * <p> * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas - * {@link #throttleFirst} does not tick, it just tracks passage of time. + * {@link #throttleFirst} does not tick, it just tracks the passage of time. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLast.s.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param intervalDuration @@ -13944,21 +16140,167 @@ public final Flowable<T> throttleLast(long intervalDuration, TimeUnit unit, Sche } /** - * Returns a Flowable that only emits those items emitted by the source Publisher that are not followed - * by another emitted item within a specified time window. + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. * <p> - * <em>Note:</em> If the source Publisher keeps emitting items more frequently than the length of the time - * window then no items will be emitted by the resulting Publisher. + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.png" alt=""> * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.png" alt=""> + * Unlike the option with {@link #throttleLatest(long, TimeUnit, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. * <p> - * Information on debounce vs throttle: + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @return the new Flowable instance + * @since 2.2 + * @see #throttleLatest(long, TimeUnit, boolean) + * @see #throttleLatest(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable<T> throttleLatest(long timeout, TimeUnit unit) { + return throttleLatest(timeout, unit, Schedulers.computation(), false); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.e.png" alt=""> + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Flowable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable<T> throttleLatest(long timeout, TimeUnit unit, boolean emitLast) { + return throttleLatest(timeout, unit, Schedulers.computation(), emitLast); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.s.png" alt=""> + * <p> + * Unlike the option with {@link #throttleLatest(long, TimeUnit, Scheduler, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @return the new Flowable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable<T> throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler) { + return throttleLatest(timeout, unit, scheduler, false); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.se.png" alt=""> + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable<T> throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableThrottleLatest<T>(this, timeout, unit, scheduler, emitLast)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires. The timer resets on + * each emission (alias to {@link #debounce(long, TimeUnit)}). + * <p> + * <em>Note:</em> If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> @@ -13971,8 +16313,9 @@ public final Flowable<T> throttleLast(long intervalDuration, TimeUnit unit, Sche * Publisher in which that Publisher emits no items in order for the item to be emitted by the * resulting Publisher * @param unit - * the {@link TimeUnit} of {@code timeout} - * @return a Flowable that filters out items that are too quickly followed by newer items + * the unit of time for the specified {@code timeout} + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Backpressure">RxJava wiki: Backpressure</a> * @see #debounce(long, TimeUnit) @@ -13985,27 +16328,19 @@ public final Flowable<T> throttleWithTimeout(long timeout, TimeUnit unit) { } /** - * Returns a Flowable that only emits those items emitted by the source Publisher that are not followed - * by another emitted item within a specified time window, where the time window is governed by a specified - * Scheduler. + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission (alias to {@link #debounce(long, TimeUnit, Scheduler)}). * <p> - * <em>Note:</em> If the source Publisher keeps emitting items more frequently than the length of the time - * window then no items will be emitted by the resulting Publisher. + * <em>Note:</em> If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.s.png" alt=""> - * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as it uses time to control data flow.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -14013,11 +16348,12 @@ public final Flowable<T> throttleWithTimeout(long timeout, TimeUnit unit) { * Publisher in which that Publisher emits no items in order for the item to be emitted by the * resulting Publisher * @param unit - * the {@link TimeUnit} of {@code timeout} + * the unit of time for the specified {@code timeout} * @param scheduler * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each * item - * @return a Flowable that filters out items that are too quickly followed by newer items + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Backpressure">RxJava wiki: Backpressure</a> * @see #debounce(long, TimeUnit, Scheduler) @@ -14201,6 +16537,7 @@ public final <V> Flowable<T> timeout(Function<? super T, ? extends Publisher<V>> * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <V> Flowable<T> timeout(Function<? super T, ? extends Publisher<V>> itemTimeoutIndicator, Flowable<? extends T> other) { @@ -14240,7 +16577,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit) { /** * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting Publisher begins instead to mirror a fallback Publisher. + * the source Publisher is disposed and resulting Publisher begins instead to mirror a fallback Publisher. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2.png" alt=""> * <dl> @@ -14263,6 +16600,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit) { * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Publisher<? extends T> other) { @@ -14273,7 +16611,8 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Publisher<? ex /** * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration - * starting from its predecessor, the resulting Publisher begins instead to mirror a fallback Publisher. + * starting from its predecessor, the source Publisher is disposed and resulting Publisher begins + * instead to mirror a fallback Publisher. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2s.png" alt=""> * <dl> @@ -14283,7 +16622,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Publisher<? ex * If any of the source {@code Publisher}s violate this, it <em>may</em> throw an * {@code IllegalStateException} when the source {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -14299,6 +16638,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Publisher<? ex * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, Publisher<? extends T> other) { @@ -14308,7 +16648,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler sche /** * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted - * item, where this policy is governed on a specified Scheduler. If the next item isn't emitted within the + * item, where this policy is governed by a specified Scheduler. If the next item isn't emitted within the * specified timeout duration starting from its predecessor, the resulting Publisher terminates and * notifies Subscribers of a {@code TimeoutException}. * <p> @@ -14318,7 +16658,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler sche * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure * behavior.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -14350,7 +16690,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler sche * are expected to honor backpressure as well. If any of then violates this rule, it <em>may</em> throw an * {@code IllegalStateException} when the {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>{@code timeout} does not operates by default on any {@link Scheduler}.</dd> + * <dd>{@code timeout} does not operate by default on any {@link Scheduler}.</dd> * </dl> * * @param <U> @@ -14370,6 +16710,7 @@ public final Flowable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler sche * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <U, V> Flowable<T> timeout(Publisher<U> firstTimeoutIndicator, @@ -14391,7 +16732,7 @@ public final <U, V> Flowable<T> timeout(Publisher<U> firstTimeoutIndicator, * If any of the source {@code Publisher}s violate this, it <em>may</em> throw an * {@code IllegalStateException} when the source {@code Publisher} completes.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>{@code timeout} does not operates by default on any {@link Scheduler}.</dd> + * <dd>{@code timeout} does not operate by default on any {@link Scheduler}.</dd> * </dl> * * @param <U> @@ -14415,6 +16756,7 @@ public final <U, V> Flowable<T> timeout(Publisher<U> firstTimeoutIndicator, * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, V> Flowable<T> timeout( @@ -14539,6 +16881,7 @@ public final Flowable<Timed<T>> timestamp(TimeUnit unit) { * @see <a href="http://reactivex.io/documentation/operators/timestamp.html">ReactiveX operators documentation: Timestamp</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. public final Flowable<Timed<T>> timestamp(final TimeUnit unit, final Scheduler scheduler) { @@ -14683,12 +17026,16 @@ public final <U extends Collection<? super T>> Single<U> toList(Callable<U> coll } /** - * Returns a Single that emits a single HashMap containing all items emitted by the source Publisher, + * Returns a Single that emits a single HashMap containing all items emitted by the finite source Publisher, * mapped by the keys returned by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.png" alt=""> * <p> * If more than one source item maps to the same key, the HashMap will contain the latest of those items. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14705,6 +17052,7 @@ public final <U extends Collection<? super T>> Single<U> toList(Callable<U> coll * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX operators documentation: To</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <K> Single<Map<K, T>> toMap(final Function<? super T, ? extends K> keySelector) { @@ -14714,12 +17062,16 @@ public final <K> Single<Map<K, T>> toMap(final Function<? super T, ? extends K> /** * Returns a Single that emits a single HashMap containing values corresponding to items emitted by the - * source Publisher, mapped by the keys returned by a specified {@code keySelector} function. + * finite source Publisher, mapped by the keys returned by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.png" alt=""> * <p> * If more than one source item maps to the same key, the HashMap will contain a single entry that * corresponds to the latest of those items. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14739,6 +17091,7 @@ public final <K> Single<Map<K, T>> toMap(final Function<? super T, ? extends K> * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX operators documentation: To</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <K, V> Single<Map<K, V>> toMap(final Function<? super T, ? extends K> keySelector, final Function<? super T, ? extends V> valueSelector) { @@ -14749,9 +17102,13 @@ public final <K, V> Single<Map<K, V>> toMap(final Function<? super T, ? extends /** * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that - * contains keys and values extracted from the items emitted by the source Publisher. + * contains keys and values extracted from the items emitted by the finite source Publisher. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14773,6 +17130,7 @@ public final <K, V> Single<Map<K, V>> toMap(final Function<? super T, ? extends * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX operators documentation: To</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <K, V> Single<Map<K, V>> toMap(final Function<? super T, ? extends K> keySelector, @@ -14785,9 +17143,13 @@ public final <K, V> Single<Map<K, V>> toMap(final Function<? super T, ? extends /** * Returns a Single that emits a single HashMap that contains an ArrayList of items emitted by the - * source Publisher keyed by a specified {@code keySelector} function. + * finite source Publisher keyed by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>This operator does not support backpressure as by intent it is requesting and buffering everything.</dd> @@ -14814,10 +17176,14 @@ public final <K> Single<Map<K, Collection<T>>> toMultimap(Function<? super T, ? /** * Returns a Single that emits a single HashMap that contains an ArrayList of values extracted by a - * specified {@code valueSelector} function from items emitted by the source Publisher, keyed by a + * specified {@code valueSelector} function from items emitted by the finite source Publisher, keyed by a * specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14848,9 +17214,13 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap(Function<? super T, /** * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that * contains a custom collection of values, extracted by a specified {@code valueSelector} function from - * items emitted by the source Publisher, and keyed by the {@code keySelector} function. + * items emitted by the finite source Publisher, and keyed by the {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14874,6 +17244,7 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap(Function<? super T, * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX operators documentation: To</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final <K, V> Single<Map<K, Collection<V>>> toMultimap( @@ -14891,9 +17262,13 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap( /** * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that * contains an ArrayList of values, extracted by a specified {@code valueSelector} function from items - * emitted by the source Publisher and keyed by the {@code keySelector} function. + * emitted by the finite source Publisher and keyed by the {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14938,14 +17313,14 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap( * @since 2.0 */ @CheckReturnValue - @BackpressureSupport(BackpressureKind.NONE) + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Observable<T> toObservable() { return RxJavaPlugins.onAssembly(new ObservableFromPublisher<T>(this)); } /** - * Returns a Single that emits a list that contains the items emitted by the source Publisher, in a + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a * sorted order. Each item emitted by the Publisher must implement {@link Comparable} with respect to all * other items in the sequence. * @@ -14954,6 +17329,10 @@ public final Observable<T> toObservable() { * sequence is terminated with a {@link ClassCastException}. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14973,10 +17352,14 @@ public final Single<List<T>> toSortedList() { } /** - * Returns a Single that emits a list that contains the items emitted by the source Publisher, in a + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a * sorted order based on a specified comparison function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.f.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -14993,6 +17376,7 @@ public final Single<List<T>> toSortedList() { * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX operators documentation: To</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<List<T>> toSortedList(final Comparator<? super T> comparator) { @@ -15001,10 +17385,14 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator } /** - * Returns a Single that emits a list that contains the items emitted by the source Publisher, in a + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a * sorted order based on a specified comparison function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.f.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -15024,6 +17412,7 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @SchedulerSupport(SchedulerSupport.NONE) public final Single<List<T>> toSortedList(final Comparator<? super T> comparator, int capacityHint) { @@ -15032,7 +17421,7 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator } /** - * Returns a Flowable that emits a list that contains the items emitted by the source Publisher, in a + * Returns a Flowable that emits a list that contains the items emitted by the finite source Publisher, in a * sorted order. Each item emitted by the Publisher must implement {@link Comparable} with respect to all * other items in the sequence. * @@ -15041,6 +17430,10 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator * sequence is terminated with a {@link ClassCastException}. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream and consumes the source {@code Publisher} in an @@ -15071,7 +17464,7 @@ public final Single<List<T>> toSortedList(int capacityHint) { * <dd>The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure * behavior.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -15081,6 +17474,7 @@ public final Single<List<T>> toSortedList(int capacityHint) { * @see <a href="http://reactivex.io/documentation/operators/subscribeon.html">ReactiveX operators documentation: SubscribeOn</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<T> unsubscribeOn(Scheduler scheduler) { @@ -15237,7 +17631,7 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit * backpressure but have an unbounded inner buffer that <em>may</em> lead to {@code OutOfMemoryError} * if left unconsumed.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15274,7 +17668,7 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit * backpressure but have an unbounded inner buffer that <em>may</em> lead to {@code OutOfMemoryError} * if left unconsumed.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15291,6 +17685,7 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit * @see <a href="http://reactivex.io/documentation/operators/window.html">ReactiveX operators documentation: Window</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, int bufferSize) { @@ -15428,7 +17823,7 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit, * backpressure but have an unbounded inner buffer that <em>may</em> lead to {@code OutOfMemoryError} * if left unconsumed.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15465,7 +17860,7 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit, * time to control the creation of windows. The returned inner {@code Publisher}s honor * backpressure and may hold up to {@code count} elements at most.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15505,7 +17900,7 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit, * time to control the creation of windows. The returned inner {@code Publisher}s honor * backpressure and may hold up to {@code count} elements at most.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15547,7 +17942,7 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit, * time to control the creation of windows. The returned inner {@code Publisher}s honor * backpressure and may hold up to {@code count} elements at most.</dd> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -15569,6 +17964,7 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit, * @see <a href="http://reactivex.io/documentation/operators/window.html">ReactiveX operators documentation: Window</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.CUSTOM) public final Flowable<Flowable<T>> window( @@ -15637,6 +18033,7 @@ public final <B> Flowable<Flowable<T>> window(Publisher<B> boundaryIndicator) { * @see <a href="http://reactivex.io/documentation/operators/window.html">ReactiveX operators documentation: Window</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <B> Flowable<Flowable<T>> window(Publisher<B> boundaryIndicator, int bufferSize) { @@ -15713,6 +18110,7 @@ public final <U, V> Flowable<Flowable<T>> window( * @see <a href="http://reactivex.io/documentation/operators/window.html">ReactiveX operators documentation: Window</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <U, V> Flowable<Flowable<T>> window( @@ -15786,6 +18184,7 @@ public final <B> Flowable<Flowable<T>> window(Callable<? extends Publisher<B>> b * @see <a href="http://reactivex.io/documentation/operators/window.html">ReactiveX operators documentation: Window</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.ERROR) @SchedulerSupport(SchedulerSupport.NONE) public final <B> Flowable<Flowable<T>> window(Callable<? extends Publisher<B>> boundaryIndicatorSupplier, int bufferSize) { @@ -15823,6 +18222,7 @@ public final <B> Flowable<Flowable<T>> window(Callable<? extends Publisher<B>> b * @see <a href="http://reactivex.io/documentation/operators/combinelatest.html">ReactiveX operators documentation: CombineLatest</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Flowable<R> withLatestFrom(Publisher<? extends U> other, @@ -15860,6 +18260,7 @@ public final <U, R> Flowable<R> withLatestFrom(Publisher<? extends U> other, * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <T1, T2, R> Flowable<R> withLatestFrom(Publisher<T1> source1, Publisher<T2> source2, @@ -15899,6 +18300,7 @@ public final <T1, T2, R> Flowable<R> withLatestFrom(Publisher<T1> source1, Publi * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <T1, T2, T3, R> Flowable<R> withLatestFrom( @@ -15943,6 +18345,7 @@ public final <T1, T2, T3, R> Flowable<R> withLatestFrom( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <T1, T2, T3, T4, R> Flowable<R> withLatestFrom( @@ -15981,6 +18384,7 @@ public final <T1, T2, T3, T4, R> Flowable<R> withLatestFrom( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> withLatestFrom(Publisher<?>[] others, Function<? super Object[], R> combiner) { @@ -16013,6 +18417,7 @@ public final <R> Flowable<R> withLatestFrom(Publisher<?>[] others, Function<? su * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> withLatestFrom(Iterable<? extends Publisher<?>> others, Function<? super Object[], R> combiner) { @@ -16052,6 +18457,7 @@ public final <R> Flowable<R> withLatestFrom(Iterable<? extends Publisher<?>> oth * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Flowable<R> zipWith(Iterable<U> other, BiFunction<? super T, ? super U, ? extends R> zipper) { @@ -16064,9 +18470,8 @@ public final <U, R> Flowable<R> zipWith(Iterable<U> other, BiFunction<? super T * Returns a Flowable that emits items that are the result of applying a specified function to pairs of * values, one each from the source Publisher and another specified Publisher. * <p> - * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -16076,7 +18481,7 @@ public final <U, R> Flowable<R> zipWith(Iterable<U> other, BiFunction<? super T * <br>To work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -16101,6 +18506,7 @@ public final <U, R> Flowable<R> zipWith(Iterable<U> other, BiFunction<? super T * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Flowable<R> zipWith(Publisher<? extends U> other, BiFunction<? super T, ? super U, ? extends R> zipper) { @@ -16112,9 +18518,8 @@ public final <U, R> Flowable<R> zipWith(Publisher<? extends U> other, BiFunction * Returns a Flowable that emits items that are the result of applying a specified function to pairs of * values, one each from the source Publisher and another specified Publisher. * <p> - * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -16124,7 +18529,7 @@ public final <U, R> Flowable<R> zipWith(Publisher<? extends U> other, BiFunction * <br>To work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -16163,9 +18568,8 @@ public final <U, R> Flowable<R> zipWith(Publisher<? extends U> other, * Returns a Flowable that emits items that are the result of applying a specified function to pairs of * values, one each from the source Publisher and another specified Publisher. * <p> - * <p> - * The operator subscribes to its sources in order they are specified and completes eagerly if - * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it + * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if * source A completes and B has been consumed and is about to complete, the operator detects A won't @@ -16175,7 +18579,7 @@ public final <U, R> Flowable<R> zipWith(Publisher<? extends U> other, * <br>To work around this termination property, * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. - * + * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> @@ -16269,7 +18673,7 @@ public final TestSubscriber<T> test(long initialRequest) { // NoPMD * <dd>{@code test} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param initialRequest the initial request amount, positive - * @param cancel should the TestSubscriber be cancelled before the subscription? + * @param cancel should the TestSubscriber be canceled before the subscription? * @return the new TestSubscriber instance * @since 2.0 */ diff --git a/src/main/java/io/reactivex/FlowableConverter.java b/src/main/java/io/reactivex/FlowableConverter.java new file mode 100644 index 0000000000..cf9176bf6d --- /dev/null +++ b/src/main/java/io/reactivex/FlowableConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Flowable#as} operator to turn a Flowable into another + * value fluently. + * <p>History: 2.1.7 - experimental + * @param <T> the upstream type + * @param <R> the output type + * @since 2.2 + */ +public interface FlowableConverter<T, R> { + /** + * Applies a function to the upstream Flowable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Flowable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Flowable<T> upstream); +} diff --git a/src/main/java/io/reactivex/FlowableEmitter.java b/src/main/java/io/reactivex/FlowableEmitter.java index 9b76cbbad0..1cd91e14b4 100644 --- a/src/main/java/io/reactivex/FlowableEmitter.java +++ b/src/main/java/io/reactivex/FlowableEmitter.java @@ -22,25 +22,44 @@ * a resource with it and exposes the current number of downstream * requested amount. * <p> - * The onNext, onError and onComplete methods should be called - * in a sequential manner, just like the Subscriber's methods. - * Use {@link #serialize()} if you want to ensure this. + * The {@link #onNext(Object)}, {@link #onError(Throwable)}, {@link #tryOnError(Throwable)} + * and {@link #onComplete()} methods should be called in a sequential manner, just like + * the {@link org.reactivestreams.Subscriber Subscriber}'s methods. + * Use the {@code FlowableEmitter} the {@link #serialize()} method returns instead of the original + * {@code FlowableEmitter} instance provided by the generator routine if you want to ensure this. * The other methods are thread-safe. + * <p> + * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onError(Throwable)}, + * {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + * <p> + * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + * <p> + * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. * * @param <T> the value type to emit */ public interface FlowableEmitter<T> extends Emitter<T> { /** - * Sets a Disposable on this emitter; any previous Disposable - * or Cancellation will be disposed/cancelled. - * @param s the disposable, null is allowed + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed */ - void setDisposable(@Nullable Disposable s); + void setDisposable(@Nullable Disposable d); /** - * Sets a Cancellable on this emitter; any previous Disposable - * or Cancellation will be disposed/cancelled. + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param c the cancellable resource, null is allowed */ void setCancellable(@Nullable Cancellable c); @@ -53,9 +72,11 @@ public interface FlowableEmitter<T> extends Emitter<T> { long requested(); /** - * Returns true if the downstream cancelled the sequence. + * Returns true if the downstream cancelled the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. * <p>This method is thread-safe. - * @return true if the downstream cancelled the sequence + * @return true if the downstream cancelled the sequence or the emitter was terminated */ boolean isCancelled(); @@ -73,11 +94,11 @@ public interface FlowableEmitter<T> extends Emitter<T> { * <p> * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called * if the error could not be delivered. + * <p>History: 2.1.1 - experimental * @param t the throwable error to signal if possible * @return true if successful, false if the downstream is not able to accept further * events - * @since 2.1.1 - experimental + * @since 2.2 */ - @Experimental boolean tryOnError(@NonNull Throwable t); } diff --git a/src/main/java/io/reactivex/FlowableOnSubscribe.java b/src/main/java/io/reactivex/FlowableOnSubscribe.java index 40911eb3cd..b5b7b83a3d 100644 --- a/src/main/java/io/reactivex/FlowableOnSubscribe.java +++ b/src/main/java/io/reactivex/FlowableOnSubscribe.java @@ -25,9 +25,9 @@ public interface FlowableOnSubscribe<T> { /** * Called for each Subscriber that subscribes. - * @param e the safe emitter instance, never null + * @param emitter the safe emitter instance, never null * @throws Exception on error */ - void subscribe(@NonNull FlowableEmitter<T> e) throws Exception; + void subscribe(@NonNull FlowableEmitter<T> emitter) throws Exception; } diff --git a/src/main/java/io/reactivex/FlowableOperator.java b/src/main/java/io/reactivex/FlowableOperator.java index 4211a3ab52..b81a0b6c7e 100644 --- a/src/main/java/io/reactivex/FlowableOperator.java +++ b/src/main/java/io/reactivex/FlowableOperator.java @@ -25,10 +25,10 @@ public interface FlowableOperator<Downstream, Upstream> { /** * Applies a function to the child Subscriber and returns a new parent Subscriber. - * @param observer the child Subscriber instance + * @param subscriber the child Subscriber instance * @return the parent Subscriber instance * @throws Exception on failure */ @NonNull - Subscriber<? super Upstream> apply(@NonNull Subscriber<? super Downstream> observer) throws Exception; + Subscriber<? super Upstream> apply(@NonNull Subscriber<? super Downstream> subscriber) throws Exception; } diff --git a/src/main/java/io/reactivex/FlowableSubscriber.java b/src/main/java/io/reactivex/FlowableSubscriber.java index e483064610..e2636406e1 100644 --- a/src/main/java/io/reactivex/FlowableSubscriber.java +++ b/src/main/java/io/reactivex/FlowableSubscriber.java @@ -17,14 +17,13 @@ import org.reactivestreams.*; /** - * Represents a Reactive-Streams inspired Subscriber that is RxJava 2 only + * Represents a Reactive Streams inspired Subscriber that is RxJava 2 only * and weakens rules §1.3 and §3.9 of the specification for gaining performance. * - * <p>History: 2.0.7 - experimental + * <p>History: 2.0.7 - experimental; 2.1 - beta * @param <T> the value type - * @since 2.1 - beta + * @since 2.2 */ -@Beta public interface FlowableSubscriber<T> extends Subscriber<T> { /** diff --git a/src/main/java/io/reactivex/Maybe.java b/src/main/java/io/reactivex/Maybe.java index a2e033acbc..aecb501348 100644 --- a/src/main/java/io/reactivex/Maybe.java +++ b/src/main/java/io/reactivex/Maybe.java @@ -27,28 +27,93 @@ import io.reactivex.internal.observers.BlockingMultiObserver; import io.reactivex.internal.operators.flowable.*; import io.reactivex.internal.operators.maybe.*; +import io.reactivex.internal.operators.mixed.*; import io.reactivex.internal.util.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; /** - * Represents a deferred computation and emission of a maybe value or exception. + * The {@code Maybe} class represents a deferred computation and emission of a single value, no value at all or an exception. + * <p> + * The {@code Maybe} class implements the {@link MaybeSource} base interface and the default consumer + * type it interacts with is the {@link MaybeObserver} via the {@link #subscribe(MaybeObserver)} method. + * <p> + * The {@code Maybe} operates with the following sequential protocol: + * <pre><code> + * onSubscribe (onSuccess | onError | onComplete)? + * </code></pre> + * <p> + * Note that {@code onSuccess}, {@code onError} and {@code onComplete} are mutually exclusive events; unlike {@code Observable}, + * {@code onSuccess} is never followed by {@code onError} or {@code onComplete}. + * <p> + * Like {@link Observable}, a running {@code Maybe} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link MaybeObserver#onSubscribe}. + * <p> + * Like an {@code Observable}, a {@code Maybe} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Maybe} instances returned by the methods of this class are <em>cold</em> + * and there is a standard <em>hot</em> implementation in the form of a subject: + * {@link io.reactivex.subjects.MaybeSubject MaybeSubject}. + * <p> + * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: * <p> * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/maybe.png" alt=""> * <p> - * The main consumer type of Maybe is {@link MaybeObserver} whose methods are called - * in a sequential fashion following this protocol:<br> - * {@code onSubscribe (onSuccess | onError | onComplete)?}. + * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. * <p> + * Example: + * <pre><code> + * Disposable d = Maybe.just("Hello World") + * .delay(10, TimeUnit.SECONDS, Schedulers.io()) + * .subscribeWith(new DisposableMaybeObserver<String>() { + * @Override + * public void onStart() { + * System.out.println("Started"); + * } + * + * @Override + * public void onSuccess(String value) { + * System.out.println("Success: " + value); + * } + * + * @Override + * public void onError(Throwable error) { + * error.printStackTrace(); + * } + * + * @Override + * public void onComplete() { + * System.out.println("Done!"); + * } + * }); + * + * Thread.sleep(5000); + * + * d.dispose(); + * </code></pre> + * <p> + * Note that by design, subscriptions via {@link #subscribe(MaybeObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(MaybeObserver)} method) and it is the + * responsibility of the implementor of the {@code MaybeObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableMaybeObserver DisposableMaybeObserver} instance. + * For convenience, the {@link #subscribeWith(MaybeObserver)} method is provided as well to + * allow working with a {@code MaybeObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). + * * @param <T> the value type * @since 2.0 + * @see io.reactivex.observers.DisposableMaybeObserver */ public abstract class Maybe<T> implements MaybeSource<T> { /** - * Runs multiple Maybe sources and signals the events of the first one that signals (cancelling + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing * the rest). + * <p> + * <img width="640" height="519" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.amb.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code amb} does not operate by default on a particular {@link Scheduler}.</dd> @@ -59,6 +124,7 @@ public abstract class Maybe<T> implements MaybeSource<T> { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> amb(final Iterable<? extends MaybeSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -66,8 +132,10 @@ public static <T> Maybe<T> amb(final Iterable<? extends MaybeSource<? extends T> } /** - * Runs multiple Maybe sources and signals the events of the first one that signals (cancelling + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing * the rest). + * <p> + * <img width="640" height="519" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.ambArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -93,6 +161,8 @@ public static <T> Maybe<T> ambArray(final MaybeSource<? extends T>... sources) { /** * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by * an Iterable sequence. + * <p> + * <img width="640" height="526" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concat.i.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -105,6 +175,7 @@ public static <T> Maybe<T> ambArray(final MaybeSource<? extends T>... sources) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat(Iterable<? extends MaybeSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -132,6 +203,7 @@ public static <T> Flowable<T> concat(Iterable<? extends MaybeSource<? extends T> */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> concat(MaybeSource<? extends T> source1, MaybeSource<? extends T> source2) { @@ -163,6 +235,7 @@ public static <T> Flowable<T> concat(MaybeSource<? extends T> source1, MaybeSour */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> concat( @@ -198,6 +271,7 @@ public static <T> Flowable<T> concat( */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> concat( @@ -212,6 +286,8 @@ public static <T> Flowable<T> concat( /** * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by * a Publisher sequence. + * <p> + * <img width="640" height="416" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concat.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer and @@ -234,6 +310,8 @@ public static <T> Flowable<T> concat(Publisher<? extends MaybeSource<? extends T /** * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by * a Publisher sequence. + * <p> + * <img width="640" height="416" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concat.pn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer and @@ -249,6 +327,7 @@ public static <T> Flowable<T> concat(Publisher<? extends MaybeSource<? extends T */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> Flowable<T> concat(Publisher<? extends MaybeSource<? extends T>> sources, int prefetch) { @@ -259,6 +338,8 @@ public static <T> Flowable<T> concat(Publisher<? extends MaybeSource<? extends T /** * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources in the array. + * <p> + * <img width="640" height="526" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatArray.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -271,6 +352,7 @@ public static <T> Flowable<T> concat(Publisher<? extends MaybeSource<? extends T */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> concatArray(MaybeSource<? extends T>... sources) { @@ -288,7 +370,7 @@ public static <T> Flowable<T> concatArray(MaybeSource<? extends T>... sources) { * Concatenates a variable number of MaybeSource sources and delays errors from any of them * till all terminate. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <img width="640" height="425" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatArrayDelayError.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream.</dd> @@ -320,6 +402,8 @@ public static <T> Flowable<T> concatArrayDelayError(MaybeSource<? extends T>... * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source MaybeSources. The operator buffers the value emitted by these MaybeSources and then drains them * in order, each one after the previous one completes. + * <p> + * <img width="640" height="489" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatArrayEager.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream.</dd> @@ -341,7 +425,8 @@ public static <T> Flowable<T> concatArrayEager(MaybeSource<? extends T>... sourc /** * Concatenates the Iterable sequence of MaybeSources into a single sequence by subscribing to each MaybeSource, * one after the other, one at a time and delays any errors till the all inner MaybeSources terminate. - * + * <p> + * <img width="640" height="469" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatDelayError.i.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator honors backpressure from downstream.</dd> @@ -356,6 +441,7 @@ public static <T> Flowable<T> concatArrayEager(MaybeSource<? extends T>... sourc @SuppressWarnings({ "unchecked", "rawtypes" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concatDelayError(Iterable<? extends MaybeSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -365,7 +451,8 @@ public static <T> Flowable<T> concatDelayError(Iterable<? extends MaybeSource<? /** * Concatenates the Publisher sequence of Publishers into a single sequence by subscribing to each inner Publisher, * one after the other, one at a time and delays any errors till the all inner and the outer Publishers terminate. - * + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatDelayError.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>{@code concatDelayError} fully supports backpressure.</dd> @@ -391,6 +478,8 @@ public static <T> Flowable<T> concatDelayError(Publisher<? extends MaybeSource<? * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source MaybeSources. The operator buffers the values emitted by these MaybeSources and then drains them * in order, each one after the previous one completes. + * <p> + * <img width="640" height="526" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatEager.i.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Backpressure is honored towards the downstream.</dd> @@ -410,11 +499,13 @@ public static <T> Flowable<T> concatEager(Iterable<? extends MaybeSource<? exten } /** - * Concatenates a Publisher sequence of Publishers eagerly into a single stream of values. + * Concatenates a Publisher sequence of MaybeSources eagerly into a single stream of values. * <p> * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * emitted source Publishers as they are observed. The operator buffers the values emitted by these * Publishers and then drains them in order, each one after the previous one completes. + * <p> + * <img width="640" height="511" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.concatEager.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>Backpressure is honored towards the downstream and the outer Publisher is @@ -463,7 +554,6 @@ public static <T> Flowable<T> concatEager(Publisher<? extends MaybeSource<? exte * * }); * </code></pre> - * <p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code create} does not operate by default on a particular {@link Scheduler}.</dd> @@ -475,6 +565,7 @@ public static <T> Flowable<T> concatEager(Publisher<? extends MaybeSource<? exte * @see Cancellable */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> create(MaybeOnSubscribe<T> onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); @@ -494,6 +585,7 @@ public static <T> Maybe<T> create(MaybeOnSubscribe<T> onSubscribe) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> defer(final Callable<? extends MaybeSource<? extends T>> maybeSupplier) { ObjectHelper.requireNonNull(maybeSupplier, "maybeSupplier is null"); @@ -538,6 +630,7 @@ public static <T> Maybe<T> empty() { * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> error(Throwable exception) { ObjectHelper.requireNonNull(exception, "exception is null"); @@ -563,6 +656,7 @@ public static <T> Maybe<T> error(Throwable exception) { * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> error(Callable<? extends Throwable> supplier) { ObjectHelper.requireNonNull(supplier, "errorSupplier is null"); @@ -575,6 +669,13 @@ public static <T> Maybe<T> error(Callable<? extends Throwable> supplier) { * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromAction} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Action} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link MaybeObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Maybe} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * @param <T> the target type * @param run the runnable to run for each subscriber @@ -582,6 +683,7 @@ public static <T> Maybe<T> error(Callable<? extends Throwable> supplier) { * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromAction(final Action run) { ObjectHelper.requireNonNull(run, "run is null"); @@ -601,6 +703,7 @@ public static <T> Maybe<T> fromAction(final Action run) { * @throws NullPointerException if completable is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromCompletable(CompletableSource completableSource) { ObjectHelper.requireNonNull(completableSource, "completableSource is null"); @@ -620,6 +723,7 @@ public static <T> Maybe<T> fromCompletable(CompletableSource completableSource) * @throws NullPointerException if single is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromSingle(SingleSource<T> singleSource) { ObjectHelper.requireNonNull(singleSource, "singleSource is null"); @@ -627,26 +731,43 @@ public static <T> Maybe<T> fromSingle(SingleSource<T> singleSource) { } /** - * Returns a {@link Maybe} that invokes passed function and emits its result for each new MaybeObserver that subscribes - * while considering {@code null} value from the callable as indication for valueless completion. + * Returns a {@link Maybe} that invokes the given {@link Callable} for each individual {@link MaybeObserver} that + * subscribes and emits the resulting non-null item via {@code onSuccess} while + * considering a {@code null} result from the {@code Callable} as indication for valueless completion + * via {@code onComplete}. + * <p> + * This operator allows you to defer the execution of the given {@code Callable} until a {@code MaybeObserver} + * subscribes to the returned {@link Maybe}. In other terms, this source operator evaluates the given + * {@code Callable} "lazily". * <p> - * Allows you to defer execution of passed function until MaybeObserver subscribes to the {@link Maybe}. - * It makes passed function "lazy". - * Result of the function invocation will be emitted by the {@link Maybe}. + * Note that the {@code null} handling of this operator differs from the similar source operators in the other + * {@link io.reactivex base reactive classes}. Those operators signal a {@code NullPointerException} if the value returned by their + * {@code Callable} is {@code null} while this {@code fromCallable} considers it to indicate the + * returned {@code Maybe} is empty. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromCallable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>Any non-fatal exception thrown by {@link Callable#call()} will be forwarded to {@code onError}, + * except if the {@code MaybeObserver} disposed the subscription in the meantime. In this latter case, + * the exception is forwarded to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} wrapped into a + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * Fatal exceptions are rethrown and usually will end up in the executing thread's + * {@link java.lang.Thread.UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} handler.</dd> * </dl> * * @param callable - * function which execution should be deferred, it will be invoked when MaybeObserver will subscribe to the {@link Maybe}. + * a {@link Callable} instance whose execution should be deferred and performed for each individual + * {@code MaybeObserver} that subscribes to the returned {@link Maybe}. * @param <T> * the type of the item emitted by the {@link Maybe}. - * @return a {@link Maybe} whose {@link MaybeObserver}s' subscriptions trigger an invocation of the given function. + * @return a new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) - public static <T> Maybe<T> fromCallable(final Callable<? extends T> callable) { + public static <T> Maybe<T> fromCallable(@NonNull final Callable<? extends T> callable) { ObjectHelper.requireNonNull(callable, "callable is null"); return RxJavaPlugins.onAssembly(new MaybeFromCallable<T>(callable)); } @@ -662,7 +783,7 @@ public static <T> Maybe<T> fromCallable(final Callable<? extends T> callable) { * <p> * <em>Important note:</em> This Maybe is blocking; you cannot dispose it. * <p> - * Unlike 1.x, cancelling the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futureMaybe.doOnDispose(() -> future.cancel(true));}. * <dl> * <dt><b>Scheduler:</b></dt> @@ -678,6 +799,7 @@ public static <T> Maybe<T> fromCallable(final Callable<? extends T> callable) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromFuture(Future<? extends T> future) { ObjectHelper.requireNonNull(future, "future is null"); @@ -693,7 +815,7 @@ public static <T> Maybe<T> fromFuture(Future<? extends T> future) { * return value of the {@link Future#get} method of that object, by passing the object into the {@code fromFuture} * method. * <p> - * Unlike 1.x, cancelling the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the * cancellation effect: {@code futureMaybe.doOnCancel(() -> future.cancel(true));}. * <p> * <em>Important note:</em> This Maybe is blocking on the thread it gets subscribed on; you cannot dispose it. @@ -715,6 +837,7 @@ public static <T> Maybe<T> fromFuture(Future<? extends T> future) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromFuture(Future<? extends T> future, long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(future, "future is null"); @@ -735,13 +858,13 @@ public static <T> Maybe<T> fromFuture(Future<? extends T> future, long timeout, * @throws NullPointerException if run is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> fromRunnable(final Runnable run) { ObjectHelper.requireNonNull(run, "run is null"); return RxJavaPlugins.onAssembly(new MaybeFromRunnable<T>(run)); } - /** * Returns a {@code Maybe} that emits a specified item. * <p> @@ -762,6 +885,7 @@ public static <T> Maybe<T> fromRunnable(final Runnable run) { * @see <a href="http://reactivex.io/documentation/operators/just.html">ReactiveX operators documentation: Just</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> just(T item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -776,10 +900,24 @@ public static <T> Maybe<T> just(T item) { * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the Iterable sequence of MaybeSource sources * @return the new Flowable instance + * @see #mergeDelayError(Iterable) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue @@ -796,10 +934,24 @@ public static <T> Flowable<T> merge(Iterable<? extends MaybeSource<? extends T>> * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the Flowable sequence of MaybeSource sources * @return the new Flowable instance + * @see #mergeDelayError(Publisher) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue @@ -816,20 +968,35 @@ public static <T> Flowable<T> merge(Publisher<? extends MaybeSource<? extends T> * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the Flowable sequence of MaybeSource sources * @param maxConcurrency the maximum number of concurrently running MaybeSources * @return the new Flowable instance + * @see #mergeDelayError(Publisher, int) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> Flowable<T> merge(Publisher<? extends MaybeSource<? extends T>> sources, int maxConcurrency) { ObjectHelper.requireNonNull(sources, "source is null"); ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); - return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, MaybeToPublisher.instance(), false, maxConcurrency, Flowable.bufferSize())); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, MaybeToPublisher.instance(), false, maxConcurrency, 1)); } /** @@ -837,10 +1004,15 @@ public static <T> Flowable<T> merge(Publisher<? extends MaybeSource<? extends T> * emitted by the nested {@code MaybeSource}, without any transformation. * <p> * <img width="640" height="393" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.merge.oo.png" alt=""> - * <p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>The resulting {@code Maybe} emits the outer source's or the inner {@code MaybeSource}'s {@code Throwable} as is. + * Unlike the other {@code merge()} operators, this operator won't and can't produce a {@code CompositeException} because there is + * only one possibility for the outer or the inner {@code MaybeSource} to emit an {@code onError} signal. + * Therefore, there is no need for a {@code mergeDelayError(MaybeSource<MaybeSource<T>>)} operator. + * </dd> * </dl> * * @param <T> the value type of the sources and the output @@ -851,6 +1023,7 @@ public static <T> Flowable<T> merge(Publisher<? extends MaybeSource<? extends T> * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> Maybe<T> merge(MaybeSource<? extends MaybeSource<? extends T>> source) { @@ -870,6 +1043,19 @@ public static <T> Maybe<T> merge(MaybeSource<? extends MaybeSource<? extends T>> * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type @@ -879,9 +1065,11 @@ public static <T> Maybe<T> merge(MaybeSource<? extends MaybeSource<? extends T>> * a MaybeSource to be merged * @return a Flowable that emits all of the items emitted by the source MaybeSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(MaybeSource, MaybeSource) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> merge( @@ -904,6 +1092,19 @@ public static <T> Flowable<T> merge( * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type @@ -915,9 +1116,11 @@ public static <T> Flowable<T> merge( * a MaybeSource to be merged * @return a Flowable that emits all of the items emitted by the source MaybeSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(MaybeSource, MaybeSource, MaybeSource) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> merge( @@ -942,6 +1145,19 @@ public static <T> Flowable<T> merge( * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource, MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type @@ -955,9 +1171,11 @@ public static <T> Flowable<T> merge( * a MaybeSource to be merged * @return a Flowable that emits all of the items emitted by the source MaybeSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(MaybeSource, MaybeSource, MaybeSource, MaybeSource) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> merge( @@ -979,13 +1197,28 @@ public static <T> Flowable<T> merge( * <dd>The operator honors backpressure from downstream.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(MaybeSource...)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the array sequence of MaybeSource sources * @return the new Flowable instance + * @see #mergeArrayDelayError(MaybeSource...) */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T> Flowable<T> mergeArray(MaybeSource<? extends T>... sources) { @@ -1037,7 +1270,6 @@ public static <T> Flowable<T> mergeArrayDelayError(MaybeSource<? extends T>... s return Flowable.fromArray(sources).flatMap((Function)MaybeToPublisher.instance(), true, sources.length); } - /** * Flattens an Iterable of MaybeSources into one Flowable, in a way that allows a Subscriber to receive all * successfully emitted items from each of the source MaybeSources without being interrupted by an error @@ -1073,15 +1305,14 @@ public static <T> Flowable<T> mergeDelayError(Iterable<? extends MaybeSource<? e return Flowable.fromIterable(sources).flatMap((Function)MaybeToPublisher.instance(), true); } - /** * Flattens a Publisher that emits MaybeSources into one Publisher, in a way that allows a Subscriber to - * receive all successfully emitted items from all of the source Publishers without being interrupted by - * an error notification from one of them. + * receive all successfully emitted items from all of the source MaybeSources without being interrupted by + * an error notification from one of them or even the main Publisher. * <p> - * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * This behaves like {@link #merge(Publisher)} except that if any of the merged MaybeSources notify of an * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that - * error notification until all of the merged Publishers have finished emitting items. + * error notification until all of the merged MaybeSources and the main Publisher have finished emitting items. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeDelayError.png" alt=""> * <p> @@ -1102,12 +1333,52 @@ public static <T> Flowable<T> mergeDelayError(Iterable<? extends MaybeSource<? e * {@code source} Publisher * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> */ - @SuppressWarnings({ "unchecked", "rawtypes" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError(Publisher<? extends MaybeSource<? extends T>> sources) { - return Flowable.fromPublisher(sources).flatMap((Function)MaybeToPublisher.instance(), true); + return mergeDelayError(sources, Integer.MAX_VALUE); + } + + /** + * Flattens a Publisher that emits MaybeSources into one Publisher, in a way that allows a Subscriber to + * receive all successfully emitted items from all of the source MaybeSources without being interrupted by + * an error notification from one of them or even the main Publisher as well as limiting the total number of active MaybeSources. + * <p> + * This behaves like {@link #merge(Publisher, int)} except that if any of the merged MaybeSources notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged MaybeSources and the main Publisher have finished emitting items. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeDelayError.png" alt=""> + * <p> + * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream. The outer {@code Publisher} is consumed + * in unbounded mode (i.e., no backpressure is applied to it).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common element base type + * @param sources + * a Publisher that emits MaybeSources + * @param maxConcurrency the maximum number of active inner MaybeSources to be merged at a time + * @return a Flowable that emits all of the items emitted by the Publishers emitted by the + * {@code source} Publisher + * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @since 2.2 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Flowable<T> mergeDelayError(Publisher<? extends MaybeSource<? extends T>> sources, int maxConcurrency) { + ObjectHelper.requireNonNull(sources, "source is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, MaybeToPublisher.instance(), true, maxConcurrency, 1)); } /** @@ -1141,6 +1412,7 @@ public static <T> Flowable<T> mergeDelayError(Publisher<? extends MaybeSource<? @SuppressWarnings({ "unchecked" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError(MaybeSource<? extends T> source1, MaybeSource<? extends T> source2) { ObjectHelper.requireNonNull(source1, "source1 is null"); @@ -1182,6 +1454,7 @@ public static <T> Flowable<T> mergeDelayError(MaybeSource<? extends T> source1, @SuppressWarnings({ "unchecked" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError(MaybeSource<? extends T> source1, MaybeSource<? extends T> source2, MaybeSource<? extends T> source3) { @@ -1191,7 +1464,6 @@ public static <T> Flowable<T> mergeDelayError(MaybeSource<? extends T> source1, return mergeArrayDelayError(source1, source2, source3); } - /** * Flattens four MaybeSources into one Flowable, in a way that allows a Subscriber to receive all * successfully emitted items from all of the source MaybeSources without being interrupted by an error @@ -1228,6 +1500,7 @@ public static <T> Flowable<T> mergeDelayError(MaybeSource<? extends T> source1, @SuppressWarnings({ "unchecked" }) @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> mergeDelayError( MaybeSource<? extends T> source1, MaybeSource<? extends T> source2, @@ -1262,7 +1535,6 @@ public static <T> Maybe<T> never() { return RxJavaPlugins.onAssembly((Maybe<T>)MaybeNever.INSTANCE); } - /** * Returns a Single that emits a Boolean value that indicates whether two MaybeSource sequences are the * same by comparing the items emitted by each MaybeSource pairwise. @@ -1312,6 +1584,7 @@ public static <T> Single<Boolean> sequenceEqual(MaybeSource<? extends T> source1 * @see <a href="http://reactivex.io/documentation/operators/sequenceequal.html">ReactiveX operators documentation: SequenceEqual</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<Boolean> sequenceEqual(MaybeSource<? extends T> source1, MaybeSource<? extends T> source2, BiPredicate<? super T, ? super T> isEqual) { @@ -1349,7 +1622,7 @@ public static Maybe<Long> timer(long delay, TimeUnit unit) { * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timer.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -1362,6 +1635,7 @@ public static Maybe<Long> timer(long delay, TimeUnit unit) { * @see <a href="http://reactivex.io/documentation/operators/timer.html">ReactiveX operators documentation: Timer</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Maybe<Long> timer(long delay, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -1382,6 +1656,7 @@ public static Maybe<Long> timer(long delay, TimeUnit unit, Scheduler scheduler) * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> unsafeCreate(MaybeSource<T> onSubscribe) { if (onSubscribe instanceof Maybe) { @@ -1448,6 +1723,7 @@ public static <T, D> Maybe<T> using(Callable<? extends D> resourceSupplier, * @see <a href="http://reactivex.io/documentation/operators/using.html">ReactiveX operators documentation: Using</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, D> Maybe<T> using(Callable<? extends D> resourceSupplier, Function<? super D, ? extends MaybeSource<? extends T>> sourceSupplier, @@ -1470,6 +1746,7 @@ public static <T, D> Maybe<T> using(Callable<? extends D> resourceSupplier, * @return the Maybe wrapper or the source cast to Maybe (if possible) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Maybe<T> wrap(MaybeSource<T> source) { if (source instanceof Maybe) { @@ -1507,6 +1784,7 @@ public static <T> Maybe<T> wrap(MaybeSource<T> source) { * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Maybe<R> zip(Iterable<? extends MaybeSource<? extends T>> sources, Function<? super Object[], ? extends R> zipper) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1541,6 +1819,7 @@ public static <T, R> Maybe<R> zip(Iterable<? extends MaybeSource<? extends T>> s */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, @@ -1580,6 +1859,7 @@ public static <T1, T2, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1623,6 +1903,7 @@ public static <T1, T2, T3, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1671,6 +1952,7 @@ public static <T1, T2, T3, T4, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1723,6 +2005,7 @@ public static <T1, T2, T3, T4, T5, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1779,6 +2062,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1840,6 +2124,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1905,6 +2190,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Maybe<R> zip( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Maybe<R> zip( MaybeSource<? extends T1> source1, MaybeSource<? extends T2> source2, MaybeSource<? extends T3> source3, @@ -1952,6 +2238,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Maybe<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Maybe<R> zipArray(Function<? super Object[], ? extends R> zipper, MaybeSource<? extends T>... sources) { @@ -1985,18 +2272,44 @@ public static <T, R> Maybe<R> zipArray(Function<? super Object[], ? extends R> z */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> ambWith(MaybeSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); return ambArray(this, other); } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * This allows fluent conversion to any other type. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code as} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current Maybe instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> R as(@NonNull MaybeConverter<T, ? extends R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + /** * Waits in a blocking fashion until the current Maybe signals a success value (which is returned), * null if completed or an exception (which is propagated). * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingGet} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * @return the success value */ @@ -2014,6 +2327,10 @@ public final T blockingGet() { * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingGet} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * @param defaultValue the default item to return if this Maybe is empty * @return the success value @@ -2064,6 +2381,7 @@ public final Maybe<T> cache() { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<U> cast(final Class<? extends U> clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -2112,13 +2430,13 @@ public final <R> Maybe<R> compose(MaybeTransformer<? super T, ? extends R> trans * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> concatMap(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new MaybeFlatten<T, R>(this, mapper)); } - /** * Returns a Flowable that emits the items emitted from the current MaybeSource, then the next, one after * the other, without interleaving them. @@ -2139,6 +2457,7 @@ public final <R> Maybe<R> concatMap(Function<? super T, ? extends MaybeSource<? */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> concatWith(MaybeSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2162,6 +2481,7 @@ public final Flowable<T> concatWith(MaybeSource<? extends T> other) { * @see <a href="http://reactivex.io/documentation/operators/contains.html">ReactiveX operators documentation: Contains</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<Boolean> contains(final Object item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -2169,7 +2489,7 @@ public final Single<Boolean> contains(final Object item) { } /** - * Returns a Maybe that counts the total number of items emitted (0 or 1) by the source Maybe and emits + * Returns a Single that counts the total number of items emitted (0 or 1) by the source Maybe and emits * this count as a 64-bit Long. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/longCount.png" alt=""> @@ -2193,6 +2513,10 @@ public final Single<Long> count() { * Returns a Maybe that emits the item emitted by the source Maybe or a specified default item * if the source Maybe is empty. * <p> + * Note that the result Maybe is semantically equivalent to a {@code Single}, since it's guaranteed + * to emit exactly one item or an error. See {@link #toSingle(Object)} for a method with equivalent + * behavior which returns a {@code Single}. + * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/defaultIfEmpty.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> @@ -2206,13 +2530,13 @@ public final Single<Long> count() { * @see <a href="http://reactivex.io/documentation/operators/defaultifempty.html">ReactiveX operators documentation: DefaultIfEmpty</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> defaultIfEmpty(T defaultItem) { - ObjectHelper.requireNonNull(defaultItem, "item is null"); + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); return switchIfEmpty(just(defaultItem)); } - /** * Returns a Maybe that signals the events emitted by the source Maybe shifted forward in time by a * specified delay. @@ -2243,7 +2567,7 @@ public final Maybe<T> delay(long delay, TimeUnit unit) { * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>you specify which {@link Scheduler} this operator will use</dd> + * <dd>you specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -2256,6 +2580,7 @@ public final Maybe<T> delay(long delay, TimeUnit unit) { * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe<T> delay(long delay, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2267,7 +2592,6 @@ public final Maybe<T> delay(long delay, TimeUnit unit, Scheduler scheduler) { * Delays the emission of this Maybe until the given Publisher signals an item or completes. * <p> * <img width="640" height="450" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.oo.png" alt=""> - * <p> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The {@code delayIndicator} is consumed in an unbounded manner but is cancelled after @@ -2287,6 +2611,7 @@ public final Maybe<T> delay(long delay, TimeUnit unit, Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/delay.html">ReactiveX operators documentation: Delay</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) public final <U, V> Maybe<T> delay(Publisher<U> delayIndicator) { @@ -2297,7 +2622,6 @@ public final <U, V> Maybe<T> delay(Publisher<U> delayIndicator) { /** * Returns a Maybe that delays the subscription to this Maybe * until the other Publisher emits an element or completes normally. - * <p> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The {@code Publisher} source is consumed in an unbounded fashion (without applying backpressure).</dd> @@ -2313,6 +2637,7 @@ public final <U, V> Maybe<T> delay(Publisher<U> delayIndicator) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> delaySubscription(Publisher<U> subscriptionIndicator) { ObjectHelper.requireNonNull(subscriptionIndicator, "subscriptionIndicator is null"); @@ -2348,7 +2673,7 @@ public final Maybe<T> delaySubscription(long delay, TimeUnit unit) { * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delaySubscription.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -2381,9 +2706,10 @@ public final Maybe<T> delaySubscription(long delay, TimeUnit unit, Scheduler sch * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doAfterSuccess(Consumer<? super T> onAfterSuccess) { - ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); return RxJavaPlugins.onAssembly(new MaybeDoAfterSuccess<T>(this, onAfterSuccess)); } @@ -2405,6 +2731,7 @@ public final Maybe<T> doAfterSuccess(Consumer<? super T> onAfterSuccess) { * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doAfterTerminate(Action onAfterTerminate) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, @@ -2429,11 +2756,12 @@ public final Maybe<T> doAfterTerminate(Action onAfterTerminate) { * <dd>{@code doFinally} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * <p>History: 2.0.1 - experimental - * @param onFinally the action called when this Maybe terminates or gets cancelled + * @param onFinally the action called when this Maybe terminates or gets disposed * @return the new Maybe instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -2447,11 +2775,12 @@ public final Maybe<T> doFinally(Action onFinally) { * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param onDispose the action called when the subscription is cancelled (disposed) + * @param onDispose the action called when the subscription is disposed * @throws NullPointerException if onDispose is null * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doOnDispose(Action onDispose) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, @@ -2467,7 +2796,7 @@ public final Maybe<T> doOnDispose(Action onDispose) { /** * Modifies the source Maybe so that it invokes an action when it calls {@code onComplete}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnComplete.png" alt=""> + * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnComplete.m.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2479,6 +2808,7 @@ public final Maybe<T> doOnDispose(Action onDispose) { * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doOnComplete(Action onComplete) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, @@ -2494,6 +2824,8 @@ public final Maybe<T> doOnComplete(Action onComplete) { /** * Calls the shared consumer with the error sent via onError for each * MaybeObserver that subscribes to the current Maybe. + * <p> + * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnError.m.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2502,6 +2834,7 @@ public final Maybe<T> doOnComplete(Action onComplete) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doOnError(Consumer<? super Throwable> onError) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, @@ -2546,6 +2879,7 @@ public final Maybe<T> doOnEvent(BiConsumer<? super T, ? super Throwable> onEvent * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doOnSubscribe(Consumer<? super Disposable> onSubscribe) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, @@ -2558,9 +2892,38 @@ public final Maybe<T> doOnSubscribe(Consumer<? super Disposable> onSubscribe) { )); } + /** + * Returns a Maybe instance that calls the given onTerminate callback + * just before this Maybe completes normally or with an exception. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnTerminate.png" alt=""> + * <p> + * This differs from {@code doAfterTerminate} in that this happens <em>before</em> the {@code onComplete} or + * {@code onError} notification. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param onTerminate the action to invoke when the consumer calls {@code onComplete} or {@code onError} + * @return the new Maybe instance + * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe<T> doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new MaybeDoOnTerminate<T>(this, onTerminate)); + } + /** * Calls the shared consumer with the success value sent via onSuccess for each * MaybeObserver that subscribes to the current Maybe. + * <p> + * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnSuccess.m.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnSuccess} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2569,11 +2932,12 @@ public final Maybe<T> doOnSubscribe(Consumer<? super Disposable> onSubscribe) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> doOnSuccess(Consumer<? super T> onSuccess) { return RxJavaPlugins.onAssembly(new MaybePeek<T>(this, Functions.emptyConsumer(), // onSubscribe - ObjectHelper.requireNonNull(onSuccess, "onSubscribe is null"), + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"), Functions.emptyConsumer(), // onError Functions.EMPTY_ACTION, // onComplete Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) @@ -2599,6 +2963,7 @@ public final Maybe<T> doOnSuccess(Consumer<? super T> onSuccess) { * @see <a href="http://reactivex.io/documentation/operators/filter.html">ReactiveX operators documentation: Filter</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> filter(Predicate<? super T> predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -2623,6 +2988,7 @@ public final Maybe<T> filter(Predicate<? super T> predicate) { * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2631,9 +2997,9 @@ public final <R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? ex /** * Maps the onSuccess, onError or onComplete signals of this Maybe into MaybeSource and emits that - * MaybeSource's signals + * MaybeSource's signals. * <p> - * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.nce.png" alt=""> + * <img width="640" height="354" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.flatMap.mmm.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2651,6 +3017,7 @@ public final <R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? ex * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> flatMap( Function<? super T, ? extends MaybeSource<? extends R>> onSuccessMapper, @@ -2685,6 +3052,7 @@ public final <R> Maybe<R> flatMap( * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? extends U>> mapper, BiFunction<? super T, ? super U, ? extends R> resultSelector) { @@ -2694,8 +3062,8 @@ public final <U, R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? } /** - * Returns a Flowable that merges each item emitted by the source Maybe with the values in an - * Iterable corresponding to that item that is generated by a selector. + * Maps the success value of the upstream {@link Maybe} into an {@link Iterable} and emits its items as a + * {@link Flowable} sequence. * <p> * <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flattenAsFlowable.png" alt=""> * <dl> @@ -2715,6 +3083,7 @@ public final <U, R> Maybe<R> flatMap(Function<? super T, ? extends MaybeSource<? */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? extends Iterable<? extends U>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2722,7 +3091,8 @@ public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? exten } /** - * Returns an Observable that maps a success value into an Iterable and emits its items. + * Maps the success value of the upstream {@link Maybe} into an {@link Iterable} and emits its items as an + * {@link Observable} sequence. * <p> * <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flattenAsObservable.png" alt=""> * <dl> @@ -2739,6 +3109,7 @@ public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? exten * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Observable<U> flattenAsObservable(final Function<? super T, ? extends Iterable<? extends U>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2762,9 +3133,11 @@ public final <U> Observable<U> flattenAsObservable(final Function<? super T, ? e * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Observable<R> flatMapObservable(Function<? super T, ? extends ObservableSource<? extends R>> mapper) { - return toObservable().flatMap(mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapObservable<T, R>(this, mapper)); } /** @@ -2781,16 +3154,18 @@ public final <R> Observable<R> flatMapObservable(Function<? super T, ? extends O * * @param <R> the result value type * @param mapper - * a function that, when applied to the item emitted by the source Maybe, returns an + * a function that, when applied to the item emitted by the source Maybe, returns a * Flowable * @return the Flowable returned from {@code func} when applied to the item emitted by the source Maybe * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMapPublisher(Function<? super T, ? extends Publisher<? extends R>> mapper) { - return toFlowable().flatMap(mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapPublisher<T, R>(this, mapper)); } /** @@ -2812,6 +3187,7 @@ public final <R> Flowable<R> flatMapPublisher(Function<? super T, ? extends Publ * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> flatMapSingle(final Function<? super T, ? extends SingleSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2839,6 +3215,7 @@ public final <R> Single<R> flatMapSingle(final Function<? super T, ? extends Sin * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> flatMapSingleElement(final Function<? super T, ? extends SingleSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2862,6 +3239,7 @@ public final <R> Maybe<R> flatMapSingleElement(final Function<? super T, ? exten * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable flatMapCompletable(final Function<? super T, ? extends CompletableSource> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2870,6 +3248,8 @@ public final Completable flatMapCompletable(final Function<? super T, ? extends /** * Hides the identity of this Maybe and its Disposable. + * <p> + * <img width="640" height="300" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.hide.png" alt=""> * <p>Allows preventing certain identity-based * optimizations (fusion). * <dl> @@ -2887,7 +3267,7 @@ public final Maybe<T> hide() { /** * Ignores the item emitted by the source Maybe and only calls {@code onComplete} or {@code onError}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ignoreElements.png" alt=""> + * <img width="640" height="389" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.ignoreElement.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ignoreElement} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2922,32 +3302,157 @@ public final Single<Boolean> isEmpty() { } /** - * Lifts a function to the current Maybe and returns a new Maybe that when subscribed to will pass the - * values of the current Maybe through the MaybeOperator function. + * <strong>This method requires advanced knowledge about building operators, please consider + * other standard composition methods first;</strong> + * Returns a {@code Maybe} which, when subscribed to, invokes the {@link MaybeOperator#apply(MaybeObserver) apply(MaybeObserver)} method + * of the provided {@link MaybeOperator} for each individual downstream {@link Maybe} and allows the + * insertion of a custom operator by accessing the downstream's {@link MaybeObserver} during this subscription phase + * and providing a new {@code MaybeObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + * <p> + * Generally, such a new {@code MaybeObserver} will wrap the downstream's {@code MaybeObserver} and forwards the + * {@code onSuccess}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + * <p> + * Example: + * <pre><code> + * // Step 1: Create the consumer type that will be returned by the MaybeOperator.apply(): + * + * public final class CustomMaybeObserver<T> implements MaybeObserver<T>, Disposable { + * + * // The downstream's MaybeObserver that will receive the onXXX events + * final MaybeObserver<? super String> downstream; + * + * // The connection to the upstream source that will call this class' onXXX methods + * Disposable upstream; + * + * // The constructor takes the downstream subscriber and usually any other parameters + * public CustomMaybeObserver(MaybeObserver<? super String> downstream) { + * this.downstream = downstream; + * } + * + * // In the subscription phase, the upstream sends a Disposable to this class + * // and subsequently this class has to send a Disposable to the downstream. + * // Note that relaying the upstream's Disposable directly is not allowed in RxJava + * @Override + * public void onSubscribe(Disposable d) { + * if (upstream != null) { + * d.dispose(); + * } else { + * upstream = d; + * downstream.onSubscribe(this); + * } + * } + * + * // The upstream calls this with the next item and the implementation's + * // responsibility is to emit an item to the downstream based on the intended + * // business logic, or if it can't do so for the particular item, + * // request more from the upstream + * @Override + * public void onSuccess(T item) { + * String str = item.toString(); + * if (str.length() < 2) { + * downstream.onSuccess(str); + * } else { + * // Maybe is usually expected to produce one of the onXXX events + * downstream.onComplete(); + * } + * } + * + * // Some operators may handle the upstream's error while others + * // could just forward it to the downstream. + * @Override + * public void onError(Throwable throwable) { + * downstream.onError(throwable); + * } + * + * // When the upstream completes, usually the downstream should complete as well. + * @Override + * public void onComplete() { + * downstream.onComplete(); + * } + * + * // Some operators may use their own resources which should be cleaned up if + * // the downstream disposes the flow before it completed. Operators without + * // resources can simply forward the dispose to the upstream. + * // In some cases, a disposed flag may be set by this method so that other parts + * // of this class may detect the dispose and stop sending events + * // to the downstream. + * @Override + * public void dispose() { + * upstream.dispose(); + * } + * + * // Some operators may simply forward the call to the upstream while others + * // can return the disposed flag set in dispose(). + * @Override + * public boolean isDisposed() { + * return upstream.isDisposed(); + * } + * } + * + * // Step 2: Create a class that implements the MaybeOperator interface and + * // returns the custom consumer type from above in its apply() method. + * // Such class may define additional parameters to be submitted to + * // the custom consumer type. + * + * final class CustomMaybeOperator<T> implements MaybeOperator<String> { + * @Override + * public MaybeObserver<? super String> apply(MaybeObserver<? super T> upstream) { + * return new CustomMaybeObserver<T>(upstream); + * } + * } + * + * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it + * // or reusing an existing one. + * + * Maybe.just(5) + * .lift(new CustomMaybeOperator<Integer>()) + * .test() + * .assertResult("5"); + * + * Maybe.just(15) + * .lift(new CustomMaybeOperator<Integer>()) + * .test() + * .assertResult(); + * </code></pre> * <p> - * In other words, this allows chaining TaskExecutors together on a Maybe for acting on the values within - * the Maybe. + * Creating custom operators can be complicated and it is recommended one consults the + * <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. * <p> - * {@code task.map(...).filter(...).lift(new OperatorA()).lift(new OperatorB(...)).subscribe() } + * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Maybe} + * class and creating a {@link MaybeTransformer} with it is recommended. * <p> - * If the operator you are creating is designed to act on the item emitted by a source Maybe, use - * {@code lift}. If your operator is designed to transform the source Maybe as a whole (for instance, by - * applying a particular set of existing RxJava operators to it) use {@link #compose}. + * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code MaybeObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Maybe}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code MaybeObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code MaybeTransformer} and extending the {@code Maybe} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. * <dl> - * <dt><b>Scheduler:</b></dt> - * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link MaybeOperator} may use a {@code Scheduler} to support its own asynchronous behavior.</dd> * </dl> * - * @param <R> the downstream's value type (output) - * @param lift - * the MaybeOperator that implements the Maybe-operating function to be applied to the source Maybe - * @return a Maybe that is the result of applying the lifted Operator to the source Maybe - * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators">RxJava wiki: Implementing Your Own Operators</a> + * @param <R> the output value type + * @param lift the {@link MaybeOperator} that receives the downstream's {@code MaybeObserver} and should return + * a {@code MaybeObserver} with custom behavior to be used as the consumer for the current + * {@code Maybe}. + * @return the new Maybe instance + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> + * @see #compose(MaybeTransformer) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> lift(final MaybeOperator<? extends R, ? super T> lift) { - ObjectHelper.requireNonNull(lift, "onLift is null"); + ObjectHelper.requireNonNull(lift, "lift is null"); return RxJavaPlugins.onAssembly(new MaybeLift<T, R>(this, lift)); } @@ -2968,12 +3473,33 @@ public final <R> Maybe<R> lift(final MaybeOperator<? extends R, ? super T> lift) * @see <a href="http://reactivex.io/documentation/operators/map.html">ReactiveX operators documentation: Map</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> map(Function<? super T, ? extends R> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new MaybeMap<T, R>(this, mapper)); } + /** + * Maps the signal types of this Maybe into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + * <p> + * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/materialize.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code materialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<Notification<T>> materialize() { + return RxJavaPlugins.onAssembly(new MaybeMaterialize<T>(this)); + } + /** * Flattens this and another Maybe into a single Flowable, without any transformation. * <p> @@ -2995,6 +3521,7 @@ public final <R> Maybe<R> map(Function<? super T, ? extends R> mapper) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> mergeWith(MaybeSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3008,7 +3535,7 @@ public final Flowable<T> mergeWith(MaybeSource<? extends T> other) { * <img width="640" height="182" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.observeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>you specify which {@link Scheduler} this operator will use</dd> + * <dd>you specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -3020,6 +3547,7 @@ public final Flowable<T> mergeWith(MaybeSource<? extends T> other) { * @see #subscribeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe<T> observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -3043,6 +3571,7 @@ public final Maybe<T> observeOn(final Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/filter.html">ReactiveX operators documentation: Filter</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<U> ofType(final Class<U> clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -3063,6 +3592,7 @@ public final <U> Maybe<U> ofType(final Class<U> clazz) { * @return the value returned by the convert function */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> R to(Function<? super Maybe<T>, R> convert) { try { @@ -3096,7 +3626,7 @@ public final Flowable<T> toFlowable() { } /** - * Converts this Maybe into an Observable instance composing cancellation + * Converts this Maybe into an Observable instance composing disposal * through. * <dl> * <dt><b>Scheduler:</b></dt> @@ -3115,7 +3645,7 @@ public final Observable<T> toObservable() { } /** - * Converts this Maybe into a Single instance composing cancellation + * Converts this Maybe into a Single instance composing disposal * through and turning an empty Maybe into a Single that emits the given * value through onSuccess. * <dl> @@ -3126,6 +3656,7 @@ public final Observable<T> toObservable() { * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> toSingle(T defaultValue) { ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"); @@ -3133,7 +3664,7 @@ public final Single<T> toSingle(T defaultValue) { } /** - * Converts this Maybe into a Single instance composing cancellation + * Converts this Maybe into a Single instance composing disposal * through and turning an empty Maybe into a signal of NoSuchElementException. * <dl> * <dt><b>Scheduler:</b></dt> @@ -3154,7 +3685,7 @@ public final Single<T> toSingle() { * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @return the new Completable instance + * @return the new Maybe instance */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -3171,9 +3702,10 @@ public final Maybe<T> onErrorComplete() { * </dl> * @param predicate the predicate to call when an Throwable is emitted which should return true * if the Throwable should be swallowed and replaced with an onComplete. - * @return the new Completable instance + * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onErrorComplete(final Predicate<? super Throwable> predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -3182,7 +3714,7 @@ public final Maybe<T> onErrorComplete(final Predicate<? super Throwable> predica } /** - * Instructs a Maybe to pass control to another MaybeSource rather than invoking + * Instructs a Maybe to pass control to another {@link MaybeSource} rather than invoking * {@link MaybeObserver#onError onError} if it encounters an error. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorResumeNext.png" alt=""> @@ -3195,12 +3727,13 @@ public final Maybe<T> onErrorComplete(final Predicate<? super Throwable> predica * </dl> * * @param next - * the next Maybe source that will take over if the source Maybe encounters + * the next {@code MaybeSource} that will take over if the source Maybe encounters * an error * @return the new Maybe instance * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onErrorResumeNext(final MaybeSource<? extends T> next) { ObjectHelper.requireNonNull(next, "next is null"); @@ -3227,6 +3760,7 @@ public final Maybe<T> onErrorResumeNext(final MaybeSource<? extends T> next) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onErrorResumeNext(Function<? super Throwable, ? extends MaybeSource<? extends T>> resumeFunction) { ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); @@ -3253,6 +3787,7 @@ public final Maybe<T> onErrorResumeNext(Function<? super Throwable, ? extends Ma * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onErrorReturn(Function<? super Throwable, ? extends T> valueSupplier) { ObjectHelper.requireNonNull(valueSupplier, "valueSupplier is null"); @@ -3278,6 +3813,7 @@ public final Maybe<T> onErrorReturn(Function<? super Throwable, ? extends T> val * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onErrorReturnItem(final T item) { ObjectHelper.requireNonNull(item, "item is null"); @@ -3307,11 +3843,13 @@ public final Maybe<T> onErrorReturnItem(final T item) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> onExceptionResumeNext(final MaybeSource<? extends T> next) { ObjectHelper.requireNonNull(next, "next is null"); return RxJavaPlugins.onAssembly(new MaybeOnErrorNext<T>(this, Functions.justFunction(next), false)); } + /** * Nulls out references to the upstream producer and downstream MaybeObserver if * the sequence is terminated or downstream calls dispose(). @@ -3319,7 +3857,7 @@ public final Maybe<T> onExceptionResumeNext(final MaybeSource<? extends T> next) * <dt><b>Scheduler:</b></dt> * <dd>{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @return a Maybe which out references to the upstream producer and downstream MaybeObserver if + * @return a Maybe which nulls out references to the upstream producer and downstream MaybeObserver if * the sequence is terminated or downstream calls dispose() */ @CheckReturnValue @@ -3433,7 +3971,6 @@ public final Flowable<T> repeatWhen(final Function<? super Flowable<Object>, ? e return toFlowable().repeatWhen(handler); } - /** * Returns a Maybe that mirrors the source Maybe, resubscribing to it if it calls {@code onError} * (infinite retry count). @@ -3447,7 +3984,7 @@ public final Flowable<T> repeatWhen(final Function<? super Flowable<Object>, ? e * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * - * @return the nww Maybe instance + * @return the new Maybe instance * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @CheckReturnValue @@ -3469,7 +4006,7 @@ public final Maybe<T> retry() { * @param predicate * the predicate that determines if a resubscription may happen in case of a specific exception * and retry count - * @return the nww Maybe instance + * @return the new Maybe instance * @see #retry() * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @@ -3494,7 +4031,7 @@ public final Maybe<T> retry(BiPredicate<? super Integer, ? super Throwable> pred * </dl> * * @param count - * number of retry attempts before failing + * the number of times to resubscribe if the current Maybe fails * @return the new Maybe instance * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @@ -3511,7 +4048,7 @@ public final Maybe<T> retry(long count) { * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Maybe fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Maybe instance */ @@ -3547,6 +4084,7 @@ public final Maybe<T> retry(Predicate<? super Throwable> predicate) { * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> retryUntil(final BooleanSupplier stop) { ObjectHelper.requireNonNull(stop, "stop is null"); @@ -3562,19 +4100,19 @@ public final Maybe<T> retryUntil(final BooleanSupplier stop) { * resubscribe to the source Publisher. * <p> * <img width="640" height="430" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retryWhen.f.png" alt=""> - * + * <p> * Example: * * This retries 3 times, each time incrementing the number of seconds it waits. * * <pre><code> - * Flowable.create((FlowableEmitter<? super String> s) -> { + * Maybe.create((MaybeEmitter<? super String> s) -> { * System.out.println("subscribing"); * s.onError(new RuntimeException("always fails")); * }, BackpressureStrategy.BUFFER).retryWhen(attempts -> { * return attempts.zipWith(Publisher.range(1, 3), (n, i) -> i).flatMap(i -> { * System.out.println("delay retry by " + i + " second(s)"); - * return Publisher.timer(i, TimeUnit.SECONDS); + * return Flowable.timer(i, TimeUnit.SECONDS); * }); * }).blockingForEach(System.out::println); * </code></pre> @@ -3590,6 +4128,31 @@ public final Maybe<T> retryUntil(final BooleanSupplier stop) { * delay retry by 3 second(s) * subscribing * } </pre> + * <p> + * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + * <p> + * The following example demonstrates how to retry an asynchronous source with a delay: + * <pre><code> + * Maybe.timer(1, TimeUnit.SECONDS) + * .doOnSubscribe(s -> System.out.println("subscribing")) + * .map(v -> { throw new RuntimeException(); }) + * .retryWhen(errors -> { + * AtomicInteger counter = new AtomicInteger(); + * return errors + * .takeWhile(e -> counter.getAndIncrement() != 3) + * .flatMap(e -> { + * System.out.println("delay retry by " + counter.get() + " second(s)"); + * return Flowable.timer(counter.get(), TimeUnit.SECONDS); + * }); + * }) + * .blockingGet(); + * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3704,6 +4267,7 @@ public final Disposable subscribe(Consumer<? super T> onSuccess, Consumer<? supe * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(Consumer<? super T> onSuccess, Consumer<? super Throwable> onError, Action onComplete) { @@ -3720,7 +4284,7 @@ public final void subscribe(MaybeObserver<? super T> observer) { observer = RxJavaPlugins.onSubscribe(this, observer); - ObjectHelper.requireNonNull(observer, "observer returned by the RxJavaPlugins hook is null"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null MaybeObserver. Please check the handler provided to RxJavaPlugins.setOnMaybeSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); try { subscribeActual(observer); @@ -3735,7 +4299,10 @@ public final void subscribe(MaybeObserver<? super T> observer) { } /** - * Override this method in subclasses to handle the incoming MaybeObservers. + * Implement this method in subclasses to handle the incoming {@link MaybeObserver}s. + * <p>There is no need to call any of the plugin hooks on the current {@code Maybe} instance or + * the {@code MaybeObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(MaybeObserver)} before this method gets called. * @param observer the MaybeObserver to handle, not null */ protected abstract void subscribeActual(MaybeObserver<? super T> observer); @@ -3746,7 +4313,7 @@ public final void subscribe(MaybeObserver<? super T> observer) { * <img width="640" height="752" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.subscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>you specify which {@link Scheduler} this operator will use</dd> + * <dd>you specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -3757,6 +4324,7 @@ public final void subscribe(MaybeObserver<? super T> observer) { * @see #observeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe<T> subscribeOn(Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -3771,11 +4339,11 @@ public final Maybe<T> subscribeOn(Scheduler scheduler) { * Maybe<Integer> source = Maybe.just(1); * CompositeDisposable composite = new CompositeDisposable(); * - * MaybeObserver<Integer> ms = new MaybeObserver<>() { + * DisposableMaybeObserver<Integer> ds = new DisposableMaybeObserver<>() { * // ... * }; * - * composite.add(source.subscribeWith(ms)); + * composite.add(source.subscribeWith(ds)); * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> @@ -3798,7 +4366,6 @@ public final <E extends MaybeObserver<? super T>> E subscribeWith(E observer) { * MaybeSource if the current Maybe is empty. * <p> * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchifempty.m.png" alt=""> - * <p/> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3810,12 +4377,37 @@ public final <E extends MaybeObserver<? super T>> E subscribeWith(E observer) { * alternate MaybeSource if the source Maybe is empty. */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> switchIfEmpty(MaybeSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); return RxJavaPlugins.onAssembly(new MaybeSwitchIfEmpty<T>(this, other)); } + /** + * Returns a Single that emits the items emitted by the source Maybe or the item of an alternate + * SingleSource if the current Maybe is empty. + * <p> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchifempty.m.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.4 - experimental + * @param other + * the alternate SingleSource to subscribe to if the main does not emit any items + * @return a Single that emits the items emitted by the source Maybe or the item of an + * alternate SingleSource if the source Maybe is empty. + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<T> switchIfEmpty(SingleSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new MaybeSwitchIfEmptySingle<T>(this, other)); + } + /** * Returns a Maybe that emits the items emitted by the source Maybe until a second MaybeSource * emits an item. @@ -3835,6 +4427,7 @@ public final Maybe<T> switchIfEmpty(MaybeSource<? extends T> other) { * @see <a href="http://reactivex.io/documentation/operators/takeuntil.html">ReactiveX operators documentation: TakeUntil</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> takeUntil(MaybeSource<U> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3864,6 +4457,7 @@ public final <U> Maybe<T> takeUntil(MaybeSource<U> other) { */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> takeUntil(Publisher<U> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -3897,7 +4491,7 @@ public final Maybe<T> timeout(long timeout, TimeUnit timeUnit) { /** * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting Maybe begins instead to mirror a fallback MaybeSource. + * the source MaybeSource is disposed and resulting Maybe begins instead to mirror a fallback MaybeSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2.png" alt=""> * <dl> @@ -3915,21 +4509,23 @@ public final Maybe<T> timeout(long timeout, TimeUnit timeUnit) { * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Maybe<T> timeout(long timeout, TimeUnit timeUnit, MaybeSource<? extends T> fallback) { - ObjectHelper.requireNonNull(fallback, "other is null"); + ObjectHelper.requireNonNull(fallback, "fallback is null"); return timeout(timeout, timeUnit, Schedulers.computation(), fallback); } /** * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration - * starting from its predecessor, the resulting Maybe begins instead to mirror a fallback MaybeSource. + * starting from its predecessor, the source MaybeSource is disposed and resulting Maybe begins instead + * to mirror a fallback MaybeSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -3944,6 +4540,7 @@ public final Maybe<T> timeout(long timeout, TimeUnit timeUnit, MaybeSource<? ext * @see <a href="http://reactivex.io/documentation/operators/timeout.html">ReactiveX operators documentation: Timeout</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe<T> timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, MaybeSource<? extends T> fallback) { ObjectHelper.requireNonNull(fallback, "fallback is null"); @@ -3959,7 +4556,7 @@ public final Maybe<T> timeout(long timeout, TimeUnit timeUnit, Scheduler schedul * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.1s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -3978,18 +4575,19 @@ public final Maybe<T> timeout(long timeout, TimeUnit timeUnit, Scheduler schedul } /** - * If this Maybe source didn't signal an event before the timeoutIndicator MaybeSource signals, a - * TimeoutException is signalled instead. + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link MaybeSource} signals, a + * {@link TimeoutException} is signaled instead. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <U> the value type of the - * @param timeoutIndicator the MaybeSource that indicates the timeout by signalling onSuccess + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling onSuccess * or onComplete. * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> timeout(MaybeSource<U> timeoutIndicator) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -3997,20 +4595,21 @@ public final <U> Maybe<T> timeout(MaybeSource<U> timeoutIndicator) { } /** - * If the current Maybe source didn't signal an event before the timeoutIndicator MaybeSource signals, - * the current Maybe is cancelled and the {@code fallback} MaybeSource subscribed to + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link MaybeSource} signals, + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to * as a continuation. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <U> the value type of the - * @param timeoutIndicator the MaybeSource that indicates the timeout by signalling onSuccess - * or onComplete. - * @param fallback the MaybeSource that is subscribed to if the current Maybe times out + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete}. + * @param fallback the {@code MaybeSource} that is subscribed to if the current {@code Maybe} times out * @return the new Maybe instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> timeout(MaybeSource<U> timeoutIndicator, MaybeSource<? extends T> fallback) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4019,8 +4618,8 @@ public final <U> Maybe<T> timeout(MaybeSource<U> timeoutIndicator, MaybeSource<? } /** - * If this Maybe source didn't signal an event before the timeoutIndicator Publisher signals, a - * TimeoutException is signalled instead. + * If the current {@code Maybe} source didn't signal an event before the {@code timeoutIndicator} {@link Publisher} signals, a + * {@link TimeoutException} is signaled instead. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The {@code timeoutIndicator} {@link Publisher} is consumed in an unbounded manner and @@ -4029,12 +4628,13 @@ public final <U> Maybe<T> timeout(MaybeSource<U> timeoutIndicator, MaybeSource<? * <dd>{@code timeout} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <U> the value type of the - * @param timeoutIndicator the MaybeSource that indicates the timeout by signalling onSuccess - * or onComplete. + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete}. * @return the new Maybe instance */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> timeout(Publisher<U> timeoutIndicator) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4042,8 +4642,8 @@ public final <U> Maybe<T> timeout(Publisher<U> timeoutIndicator) { } /** - * If the current Maybe source didn't signal an event before the timeoutIndicator Publisher signals, - * the current Maybe is cancelled and the {@code fallback} MaybeSource subscribed to + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link Publisher} signals, + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to * as a continuation. * <dl> * <dt><b>Backpressure:</b></dt> @@ -4053,13 +4653,14 @@ public final <U> Maybe<T> timeout(Publisher<U> timeoutIndicator) { * <dd>{@code timeout} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <U> the value type of the - * @param timeoutIndicator the MaybeSource that indicates the timeout by signalling onSuccess - * or onComplete - * @param fallback the MaybeSource that is subscribed to if the current Maybe times out + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete} + * @param fallback the {@code MaybeSource} that is subscribed to if the current {@code Maybe} times out * @return the new Maybe instance */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Maybe<T> timeout(Publisher<U> timeoutIndicator, MaybeSource<? extends T> fallback) { ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); @@ -4069,16 +4670,17 @@ public final <U> Maybe<T> timeout(Publisher<U> timeoutIndicator, MaybeSource<? e /** * Returns a Maybe which makes sure when a MaybeObserver disposes the Disposable, - * that call is propagated up on the specified scheduler + * that call is propagated up on the specified scheduler. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.</dd> * </dl> - * @param scheduler the target scheduler where to execute the cancellation + * @param scheduler the target scheduler where to execute the disposal * @return the new Maybe instance * @throws NullPointerException if scheduler is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Maybe<T> unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -4112,6 +4714,7 @@ public final Maybe<T> unsubscribeOn(final Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U, R> Maybe<R> zipWith(MaybeSource<? extends U> other, BiFunction<? super T, ? super U, ? extends R> zipper) { ObjectHelper.requireNonNull(other, "other is null"); @@ -4134,9 +4737,9 @@ public final <U, R> Maybe<R> zipWith(MaybeSource<? extends U> other, BiFunction< @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test() { - TestObserver<T> ts = new TestObserver<T>(); - subscribe(ts); - return ts; + TestObserver<T> to = new TestObserver<T>(); + subscribe(to); + return to; } /** @@ -4152,13 +4755,13 @@ public final TestObserver<T> test() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test(boolean cancelled) { - TestObserver<T> ts = new TestObserver<T>(); + TestObserver<T> to = new TestObserver<T>(); if (cancelled) { - ts.cancel(); + to.cancel(); } - subscribe(ts); - return ts; + subscribe(to); + return to; } } diff --git a/src/main/java/io/reactivex/MaybeConverter.java b/src/main/java/io/reactivex/MaybeConverter.java new file mode 100644 index 0000000000..c997399d99 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Maybe#as} operator to turn a Maybe into another + * value fluently. + * <p>History: 2.1.7 - experimental + * @param <T> the upstream type + * @param <R> the output type + * @since 2.2 + */ +public interface MaybeConverter<T, R> { + /** + * Applies a function to the upstream Maybe and returns a converted value of type {@code R}. + * + * @param upstream the upstream Maybe instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Maybe<T> upstream); +} diff --git a/src/main/java/io/reactivex/MaybeEmitter.java b/src/main/java/io/reactivex/MaybeEmitter.java index dfe7958d11..4819ce3c3e 100644 --- a/src/main/java/io/reactivex/MaybeEmitter.java +++ b/src/main/java/io/reactivex/MaybeEmitter.java @@ -21,9 +21,29 @@ * Abstraction over an RxJava {@link MaybeObserver} that allows associating * a resource with it. * <p> - * All methods are safe to call from multiple threads. + * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. * <p> - * Calling onSuccess, onError or onComplete multiple times has no effect. + * Calling {@link #onSuccess(Object)} or {@link #onComplete()} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after the other two will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * <p> + * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onSuccess(Object)}, + * {@link #onError(Throwable)}, {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + * <p> + * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + * <p> + * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. * * @param <T> the value type to emit */ @@ -47,22 +67,26 @@ public interface MaybeEmitter<T> { void onComplete(); /** - * Sets a Disposable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. - * @param s the disposable, null is allowed + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed */ - void setDisposable(@Nullable Disposable s); + void setDisposable(@Nullable Disposable d); /** - * Sets a Cancellable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param c the cancellable resource, null is allowed */ void setCancellable(@Nullable Cancellable c); /** - * Returns true if the downstream cancelled the sequence. - * @return true if the downstream cancelled the sequence + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onSuccess(Object)}, {@link #onError(Throwable)}, + * {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. + * <p>This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated */ boolean isDisposed(); @@ -73,11 +97,11 @@ public interface MaybeEmitter<T> { * <p> * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called * if the error could not be delivered. + * <p>History: 2.1.1 - experimental * @param t the throwable error to signal if possible * @return true if successful, false if the downstream is not able to accept further * events - * @since 2.1.1 - experimental + * @since 2.2 */ - @Experimental boolean tryOnError(@NonNull Throwable t); } diff --git a/src/main/java/io/reactivex/MaybeObserver.java b/src/main/java/io/reactivex/MaybeObserver.java index 42a0ac7bfe..76784484f1 100644 --- a/src/main/java/io/reactivex/MaybeObserver.java +++ b/src/main/java/io/reactivex/MaybeObserver.java @@ -17,14 +17,37 @@ import io.reactivex.disposables.Disposable; /** - * Provides a mechanism for receiving push-based notifications. + * Provides a mechanism for receiving push-based notification of a single value, an error or completion without any value. * <p> - * After a MaybeObserver calls a {@link Maybe}'s {@link Maybe#subscribe subscribe} method, - * first the Maybe calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows - * cancelling the sequence at any time, then the - * {@code Maybe} calls only one of the MaybeObserver's {@link #onSuccess}, {@link #onError} or - * {@link #onComplete} methods to provide notifications. - * + * When a {@code MaybeObserver} is subscribed to a {@link MaybeSource} through the {@link MaybeSource#subscribe(MaybeObserver)} method, + * the {@code MaybeSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code MaybeSource} will call a {@code MaybeObserver}'s {@link #onSuccess(Object)}, {@link #onError(Throwable)} + * or {@link #onComplete()} method exactly once as they are considered mutually exclusive <strong>terminal signals</strong>. + * <p> + * Calling the {@code MaybeObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + * <pre><code> onSubscribe (onSuccess | onError | onComplete)?</code></pre> + * <p> + * Note that unlike with the {@code Observable} protocol, {@link #onComplete()} is not called after the success item has been + * signalled via {@link #onSuccess(Object)}. + * <p> + * Subscribing a {@code MaybeObserver} to multiple {@code MaybeSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code MaybeObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + * <p> + * Calling {@link #onSubscribe(Disposable)}, {@link #onSuccess(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + * <p> + * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + * <ul> + * <li>If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + * </li> + * <li>If there is a fatal error (such as {@code VirtualMachineError}).</li> + * </ul> * @see <a href="http://reactivex.io/documentation/observable.html">ReactiveX documentation: Observable</a> * @param <T> * the type of item the MaybeObserver expects to observe diff --git a/src/main/java/io/reactivex/MaybeOnSubscribe.java b/src/main/java/io/reactivex/MaybeOnSubscribe.java index 9c03275797..035a001be7 100644 --- a/src/main/java/io/reactivex/MaybeOnSubscribe.java +++ b/src/main/java/io/reactivex/MaybeOnSubscribe.java @@ -25,9 +25,9 @@ public interface MaybeOnSubscribe<T> { /** * Called for each MaybeObserver that subscribes. - * @param e the safe emitter instance, never null + * @param emitter the safe emitter instance, never null * @throws Exception on error */ - void subscribe(@NonNull MaybeEmitter<T> e) throws Exception; + void subscribe(@NonNull MaybeEmitter<T> emitter) throws Exception; } diff --git a/src/main/java/io/reactivex/Observable.java b/src/main/java/io/reactivex/Observable.java index 2afb282588..43f1f14483 100644 --- a/src/main/java/io/reactivex/Observable.java +++ b/src/main/java/io/reactivex/Observable.java @@ -26,6 +26,7 @@ import io.reactivex.internal.fuseable.ScalarCallable; import io.reactivex.internal.observers.*; import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.operators.mixed.*; import io.reactivex.internal.operators.observable.*; import io.reactivex.internal.util.*; import io.reactivex.observables.*; @@ -41,7 +42,7 @@ * Many operators in the class accept {@code ObservableSource}(s), the base reactive interface * for such non-backpressured flows, which {@code Observable} itself implements as well. * <p> - * The Observable's operators, by default, run with a buffer size of 128 elements (see {@link Flowable#bufferSize()}, + * The Observable's operators, by default, run with a buffer size of 128 elements (see {@link Flowable#bufferSize()}), * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have * overloads that allow setting their internal buffer size explicitly. * <p> @@ -50,9 +51,9 @@ * <img width="640" height="317" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/legend.png" alt=""> * <p> * The design of this class was derived from the - * <a href="https://github.com/reactive-streams/reactive-streams-jvm">Reactive-Streams design and specification</a> + * <a href="https://github.com/reactive-streams/reactive-streams-jvm">Reactive Streams design and specification</a> * by removing any backpressure-related infrastructure and implementation detail, replacing the - * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to cancel + * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to dispose of * a flow. * <p> * The {@code Observable} follows the protocol @@ -63,7 +64,7 @@ * the stream can be disposed through the {@code Disposable} instance provided to consumers through * {@code Observer.onSubscribe}. * <p> - * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external cancellation + * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external disposal * of a subscription and the {@code Observer} instance is expected to expose such capability. * <p>Example: * <pre><code> @@ -73,7 +74,7 @@ * @Override public void onStart() { * System.out.println("Start!"); * } - * @Override public void onNext(Integer t) { + * @Override public void onNext(String t) { * System.out.println(t); * } * @Override public void onError(Throwable t) { @@ -83,12 +84,12 @@ * System.out.println("Done!"); * } * }); - * + * * Thread.sleep(500); - * // the sequence now can be cancelled via dispose() + * // the sequence can now be disposed via dispose() * d.dispose(); * </code></pre> - * + * * @param <T> * the type of the items emitted by the Observable * @see Flowable @@ -115,6 +116,7 @@ public abstract class Observable<T> implements ObservableSource<T> { * @see <a href="http://reactivex.io/documentation/operators/amb.html">ReactiveX operators documentation: Amb</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> amb(Iterable<? extends ObservableSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -141,6 +143,7 @@ public static <T> Observable<T> amb(Iterable<? extends ObservableSource<? extend */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> ambArray(ObservableSource<? extends T>... sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -288,6 +291,7 @@ public static <T, R> Observable<R> combineLatest(Iterable<? extends ObservableSo * @see <a href="http://reactivex.io/documentation/operators/combinelatest.html">ReactiveX operators documentation: CombineLatest</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Observable<R> combineLatest(Iterable<? extends ObservableSource<? extends T>> sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -380,6 +384,7 @@ public static <T, R> Observable<R> combineLatest(ObservableSource<? extends T>[] * @see <a href="http://reactivex.io/documentation/operators/combinelatest.html">ReactiveX operators documentation: CombineLatest</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Observable<R> combineLatest(ObservableSource<? extends T>[] sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -425,6 +430,7 @@ public static <T, R> Observable<R> combineLatest(ObservableSource<? extends T>[] */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -467,6 +473,7 @@ public static <T1, T2, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -514,6 +521,7 @@ public static <T1, T2, T3, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -565,6 +573,7 @@ public static <T1, T2, T3, T4, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -621,6 +630,7 @@ public static <T1, T2, T3, T4, T5, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -681,6 +691,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -746,6 +757,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -815,6 +827,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Observable<R> combineLatest( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Observable<R> combineLatest( ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, @@ -961,6 +974,7 @@ public static <T, R> Observable<R> combineLatestDelayError(Function<? super Obje * @see <a href="http://reactivex.io/documentation/operators/combinelatest.html">ReactiveX operators documentation: CombineLatest</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Observable<R> combineLatestDelayError(ObservableSource<? extends T>[] sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -1056,6 +1070,7 @@ public static <T, R> Observable<R> combineLatestDelayError(Iterable<? extends Ob * @see <a href="http://reactivex.io/documentation/operators/combinelatest.html">ReactiveX operators documentation: CombineLatest</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Observable<R> combineLatestDelayError(Iterable<? extends ObservableSource<? extends T>> sources, Function<? super Object[], ? extends R> combiner, int bufferSize) { @@ -1083,6 +1098,7 @@ public static <T, R> Observable<R> combineLatestDelayError(Iterable<? extends Ob */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concat(Iterable<? extends ObservableSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1133,6 +1149,7 @@ public static <T> Observable<T> concat(ObservableSource<? extends ObservableSour */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concat(ObservableSource<? extends ObservableSource<? extends T>> sources, int prefetch) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1161,6 +1178,7 @@ public static <T> Observable<T> concat(ObservableSource<? extends ObservableSour */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concat(ObservableSource<? extends T> source1, ObservableSource<? extends T> source2) { ObjectHelper.requireNonNull(source1, "source1 is null"); @@ -1191,6 +1209,7 @@ public static <T> Observable<T> concat(ObservableSource<? extends T> source1, Ob */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concat( ObservableSource<? extends T> source1, ObservableSource<? extends T> source2, @@ -1226,6 +1245,7 @@ public static <T> Observable<T> concat( */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concat( ObservableSource<? extends T> source1, ObservableSource<? extends T> source2, @@ -1258,7 +1278,7 @@ public static <T> Observable<T> concat( public static <T> Observable<T> concatArray(ObservableSource<? extends T>... sources) { if (sources.length == 0) { return empty(); - } else + } if (sources.length == 1) { return wrap((ObservableSource<T>)sources[0]); } @@ -1285,7 +1305,7 @@ public static <T> Observable<T> concatArray(ObservableSource<? extends T>... sou public static <T> Observable<T> concatArrayDelayError(ObservableSource<? extends T>... sources) { if (sources.length == 0) { return empty(); - } else + } if (sources.length == 1) { return (Observable<T>)wrap(sources[0]); } @@ -1293,19 +1313,19 @@ public static <T> Observable<T> concatArrayDelayError(ObservableSource<? extends } /** - * Concatenates a sequence of ObservableSources eagerly into a single stream of values. + * Concatenates an array of ObservableSources eagerly into a single stream of values. + * <p> + * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatArrayEager.png" alt=""> * <p> * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them * in order, each one after the previous one completes. - * <p> - * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatArrayEager.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <T> the value type - * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @param sources an array of ObservableSources that need to be eagerly concatenated * @return the new ObservableSource instance with the specified concatenation behavior * @since 2.0 */ @@ -1316,7 +1336,9 @@ public static <T> Observable<T> concatArrayEager(ObservableSource<? extends T>.. } /** - * Concatenates a sequence of ObservableSources eagerly into a single stream of values. + * Concatenates an array of ObservableSources eagerly into a single stream of values. + * <p> + * <img width="640" height="495" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatArrayEager.nn.png" alt=""> * <p> * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them @@ -1326,7 +1348,7 @@ public static <T> Observable<T> concatArrayEager(ObservableSource<? extends T>.. * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <T> the value type - * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @param sources an array of ObservableSources that need to be eagerly concatenated * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE * is interpreted as indication to subscribe to all sources at once * @param prefetch the number of elements to prefetch from each ObservableSource source @@ -1340,6 +1362,58 @@ public static <T> Observable<T> concatArrayEager(int maxConcurrency, int prefetc return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, false); } + /** + * Concatenates an array of {@link ObservableSource}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + * <p> + * <img width="640" height="354" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatArrayEagerDelayError.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code ObservableSource}s. The operator buffers the values emitted by these {@code ObservableSource}s + * and then drains them in order, each one after the previous one completes. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources an array of {@code ObservableSource}s that need to be eagerly concatenated + * @return the new Observable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Observable<T> concatArrayEagerDelayError(ObservableSource<? extends T>... sources) { + return concatArrayEagerDelayError(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of {@link ObservableSource}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + * <p> + * <img width="640" height="460" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatArrayEagerDelayError.nn.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code ObservableSource}s. The operator buffers the values emitted by these {@code ObservableSource}s + * and then drains them in order, each one after the previous one completes. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources an array of {@code ObservableSource}s that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each {@code ObservableSource} source + * @return the new Observable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Observable<T> concatArrayEagerDelayError(int maxConcurrency, int prefetch, ObservableSource<? extends T>... sources) { + return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, true); + } + /** * Concatenates the Iterable sequence of ObservableSources into a single sequence by subscribing to each ObservableSource, * one after the other, one at a time and delays any errors till the all inner ObservableSources terminate. @@ -1355,6 +1429,7 @@ public static <T> Observable<T> concatArrayEager(int maxConcurrency, int prefetc * @return the new ObservableSource with the concatenating behavior */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concatDelayError(Iterable<? extends ObservableSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1400,6 +1475,7 @@ public static <T> Observable<T> concatDelayError(ObservableSource<? extends Obse */ @SuppressWarnings({ "rawtypes", "unchecked" }) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concatDelayError(ObservableSource<? extends ObservableSource<? extends T>> sources, int prefetch, boolean tillTheEnd) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -1454,8 +1530,6 @@ public static <T> Observable<T> concatEager(ObservableSource<? extends Observabl @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concatEager(ObservableSource<? extends ObservableSource<? extends T>> sources, int maxConcurrency, int prefetch) { - ObjectHelper.requireNonNull(maxConcurrency, "maxConcurrency is null"); - ObjectHelper.requireNonNull(prefetch, "prefetch is null"); return wrap(sources).concatMapEager((Function)Functions.identity(), maxConcurrency, prefetch); } @@ -1506,8 +1580,6 @@ public static <T> Observable<T> concatEager(Iterable<? extends ObservableSource< @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> concatEager(Iterable<? extends ObservableSource<? extends T>> sources, int maxConcurrency, int prefetch) { - ObjectHelper.requireNonNull(maxConcurrency, "maxConcurrency is null"); - ObjectHelper.requireNonNull(prefetch, "prefetch is null"); return fromIterable(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, false); } @@ -1556,6 +1628,7 @@ public static <T> Observable<T> concatEager(Iterable<? extends ObservableSource< * @see Cancellable */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> create(ObservableOnSubscribe<T> source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1587,6 +1660,7 @@ public static <T> Observable<T> create(ObservableOnSubscribe<T> source) { * @see <a href="http://reactivex.io/documentation/operators/defer.html">ReactiveX operators documentation: Defer</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> defer(Callable<? extends ObservableSource<? extends T>> supplier) { ObjectHelper.requireNonNull(supplier, "supplier is null"); @@ -1635,6 +1709,7 @@ public static <T> Observable<T> empty() { * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> error(Callable<? extends Throwable> errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -1660,9 +1735,10 @@ public static <T> Observable<T> error(Callable<? extends Throwable> errorSupplie * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> error(final Throwable exception) { - ObjectHelper.requireNonNull(exception, "e is null"); + ObjectHelper.requireNonNull(exception, "exception is null"); return error(Functions.justCallable(exception)); } @@ -1684,11 +1760,12 @@ public static <T> Observable<T> error(final Throwable exception) { */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @NonNull public static <T> Observable<T> fromArray(T... items) { ObjectHelper.requireNonNull(items, "items is null"); if (items.length == 0) { return empty(); - } else + } if (items.length == 1) { return just(items[0]); } @@ -1706,8 +1783,14 @@ public static <T> Observable<T> fromArray(T... items) { * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromCallable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Observer#onError(Throwable)}, + * except when the downstream has disposed this {@code Observable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> - * * @param supplier * a function, the execution of which should be deferred; {@code fromCallable} will invoke this * function only when an observer subscribes to the ObservableSource that {@code fromCallable} returns @@ -1718,6 +1801,7 @@ public static <T> Observable<T> fromArray(T... items) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> fromCallable(Callable<? extends T> supplier) { ObjectHelper.requireNonNull(supplier, "supplier is null"); @@ -1735,8 +1819,8 @@ public static <T> Observable<T> fromCallable(Callable<? extends T> supplier) { * <p> * <em>Important note:</em> This ObservableSource is blocking; you cannot dispose it. * <p> - * Unlike 1.x, cancelling the Observable won't cancel the future. If necessary, one can use composition to achieve the - * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future.cancel(true));}. + * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromFuture} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1751,6 +1835,7 @@ public static <T> Observable<T> fromCallable(Callable<? extends T> supplier) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> fromFuture(Future<? extends T> future) { ObjectHelper.requireNonNull(future, "future is null"); @@ -1766,8 +1851,8 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future) { * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} * method. * <p> - * Unlike 1.x, cancelling the Observable won't cancel the future. If necessary, one can use composition to achieve the - * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future.cancel(true));}. + * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. * <p> * <em>Important note:</em> This ObservableSource is blocking; you cannot dispose it. * <dl> @@ -1788,6 +1873,7 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future) { * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> fromFuture(Future<? extends T> future, long timeout, TimeUnit unit) { ObjectHelper.requireNonNull(future, "future is null"); @@ -1804,8 +1890,8 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future, long time * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} * method. * <p> - * Unlike 1.x, cancelling the Observable won't cancel the future. If necessary, one can use composition to achieve the - * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future.cancel(true));}. + * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. * <p> * <em>Important note:</em> This ObservableSource is blocking; you cannot dispose it. * <dl> @@ -1829,6 +1915,7 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future, long time * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static <T> Observable<T> fromFuture(Future<? extends T> future, long timeout, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1845,11 +1932,11 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future, long time * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} * method. * <p> - * Unlike 1.x, cancelling the Observable won't cancel the future. If necessary, one can use composition to achieve the - * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future.cancel(true));}. + * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param future @@ -1864,6 +1951,7 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future, long time * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static <T> Observable<T> fromFuture(Future<? extends T> future, Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -1889,6 +1977,7 @@ public static <T> Observable<T> fromFuture(Future<? extends T> future, Scheduler * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> fromIterable(Iterable<? extends T> source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1896,7 +1985,20 @@ public static <T> Observable<T> fromIterable(Iterable<? extends T> source) { } /** - * Converts an arbitrary Reactive-Streams Publisher into an Observable. + * Converts an arbitrary Reactive Streams Publisher into an Observable. + * <p> + * <img width="640" height="344" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/fromPublisher.o.png" alt=""> + * <p> + * The {@link Publisher} must follow the + * <a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams">Reactive Streams specification</a>. + * Violating the specification may result in undefined behavior. + * <p> + * If possible, use {@link #create(ObservableOnSubscribe)} to create a + * source-like {@code Observable} instead. + * <p> + * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The source {@code publisher} is consumed in an unbounded fashion without applying any @@ -1908,9 +2010,11 @@ public static <T> Observable<T> fromIterable(Iterable<? extends T> source) { * @param publisher the Publisher to convert * @return the new Observable instance * @throws NullPointerException if publisher is null + * @see #create(ObservableOnSubscribe) */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> fromPublisher(Publisher<? extends T> publisher) { ObjectHelper.requireNonNull(publisher, "publisher is null"); @@ -1921,6 +2025,11 @@ public static <T> Observable<T> fromPublisher(Publisher<? extends T> publisher) * Returns a cold, synchronous and stateless generator of values. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/generate.2.png" alt=""> + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code generate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1934,9 +2043,10 @@ public static <T> Observable<T> fromPublisher(Publisher<? extends T> publisher) * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> generate(final Consumer<Emitter<T>> generator) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(Functions.<Object>nullSupplier(), ObservableInternalHelper.simpleGenerator(generator), Functions.<Object>emptyConsumer()); } @@ -1945,6 +2055,11 @@ public static <T> Observable<T> generate(final Consumer<Emitter<T>> generator) { * Returns a cold, synchronous and stateful generator of values. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/generate.2.png" alt=""> + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code generate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1960,9 +2075,10 @@ public static <T> Observable<T> generate(final Consumer<Emitter<T>> generator) { * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Observable<T> generate(Callable<S> initialState, final BiConsumer<S, Emitter<T>> generator) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), Functions.emptyConsumer()); } @@ -1970,6 +2086,11 @@ public static <T, S> Observable<T> generate(Callable<S> initialState, final BiCo * Returns a cold, synchronous and stateful generator of values. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/generate.2.png" alt=""> + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code generate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1983,16 +2104,17 @@ public static <T, S> Observable<T> generate(Callable<S> initialState, final BiCo * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets disposed * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Observable<T> generate( final Callable<S> initialState, final BiConsumer<S, Emitter<T>> generator, Consumer<? super S> disposeState) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), disposeState); } @@ -2000,6 +2122,11 @@ public static <T, S> Observable<T> generate( * Returns a cold, synchronous and stateful generator of values. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/generate.2.png" alt=""> + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code generate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2025,6 +2152,11 @@ public static <T, S> Observable<T> generate(Callable<S> initialState, BiFunction * Returns a cold, synchronous and stateful generator of values. * <p> * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/generate.2.png" alt=""> + * <p> + * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code generate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2039,15 +2171,16 @@ public static <T, S> Observable<T> generate(Callable<S> initialState, BiFunction * the next invocation. Signalling multiple {@code onNext} * in a call will make the operator signal {@code IllegalStateException}. * @param disposeState the Consumer that is called with the current state when the generator - * terminates the sequence or it gets cancelled + * terminates the sequence or it gets disposed * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, S> Observable<T> generate(Callable<S> initialState, BiFunction<S, Emitter<T>, S> generator, Consumer<? super S> disposeState) { ObjectHelper.requireNonNull(initialState, "initialState is null"); - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); ObjectHelper.requireNonNull(disposeState, "disposeState is null"); return RxJavaPlugins.onAssembly(new ObservableGenerate<T, S>(initialState, generator, disposeState)); } @@ -2086,7 +2219,7 @@ public static Observable<Long> interval(long initialDelay, long period, TimeUnit * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timer.ps.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param initialDelay @@ -2103,6 +2236,7 @@ public static Observable<Long> interval(long initialDelay, long period, TimeUnit * @since 1.0.12 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable<Long> interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -2140,7 +2274,7 @@ public static Observable<Long> interval(long period, TimeUnit unit) { * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/interval.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param period @@ -2199,6 +2333,7 @@ public static Observable<Long> intervalRange(long start, long count, long initia * @return the new Observable instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Observable<Long> intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { if (count < 0) { @@ -2220,17 +2355,17 @@ public static Observable<Long> intervalRange(long start, long count, long initia } /** - * Returns an Observable that emits a single item and then completes. + * Returns an Observable that signals the given (constant reference) item and then completes. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.png" alt=""> + * <img width="640" height="290" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.item.png" alt=""> * <p> - * To convert any object into an ObservableSource that emits that object, pass that object into the {@code just} - * method. + * Note that the item is taken and re-emitted as is and not computed by any means by {@code just}. Use {@link #fromCallable(Callable)} + * to generate a single item on demand (when {@code Observer}s subscribe to it). + * <p> + * See the multi-parameter overloads of {@code just} to emit more than one (constant reference) items one after the other. + * Use {@link #fromArray(Object...)} to emit an arbitrary number of items that are known upfront. * <p> - * This is similar to the {@link #fromArray(java.lang.Object[])} method, except that {@code from} will convert - * an {@link Iterable} object into an ObservableSource that emits each of the items in the Iterable, one at a - * time, while the {@code just} method converts an Iterable into an ObservableSource that emits the entire - * Iterable as a single item. + * To emit the items of an {@link Iterable} sequence (such as a {@link java.util.List}), use {@link #fromIterable(Iterable)}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2242,18 +2377,23 @@ public static Observable<Long> intervalRange(long start, long count, long initia * the type of that item * @return an Observable that emits {@code value} as a single item and then completes * @see <a href="http://reactivex.io/documentation/operators/just.html">ReactiveX operators documentation: Just</a> + * @see #just(Object, Object) + * @see #fromCallable(Callable) + * @see #fromArray(Object...) + * @see #fromIterable(Iterable) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item) { - ObjectHelper.requireNonNull(item, "The item is null"); + ObjectHelper.requireNonNull(item, "item is null"); return RxJavaPlugins.onAssembly(new ObservableJust<T>(item)); } /** * Converts two items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.2.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2270,10 +2410,11 @@ public static <T> Observable<T> just(T item) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); return fromArray(item1, item2); } @@ -2281,7 +2422,7 @@ public static <T> Observable<T> just(T item1, T item2) { /** * Converts three items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.3.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2300,11 +2441,12 @@ public static <T> Observable<T> just(T item1, T item2) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); return fromArray(item1, item2, item3); } @@ -2312,7 +2454,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3) { /** * Converts four items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.4.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2333,12 +2475,13 @@ public static <T> Observable<T> just(T item1, T item2, T item3) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); return fromArray(item1, item2, item3, item4); } @@ -2346,7 +2489,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4) { /** * Converts five items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.5.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2369,13 +2512,14 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4) { */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); return fromArray(item1, item2, item3, item4, item5); } @@ -2383,7 +2527,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 /** * Converts six items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.6.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2408,14 +2552,15 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); return fromArray(item1, item2, item3, item4, item5, item6); } @@ -2423,7 +2568,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 /** * Converts seven items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.7.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2450,15 +2595,16 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7); } @@ -2466,7 +2612,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 /** * Converts eight items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.8.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2495,16 +2641,17 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); } @@ -2512,7 +2659,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 /** * Converts nine items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.9.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2543,17 +2690,18 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); } @@ -2561,7 +2709,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 /** * Converts ten items into an ObservableSource that emits those items. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.m.png" alt=""> + * <img width="640" height="186" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/just.10.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code just} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2594,18 +2742,19 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 */ @SuppressWarnings("unchecked") @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); - ObjectHelper.requireNonNull(item10, "The tenth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } @@ -2621,6 +2770,19 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2635,6 +2797,7 @@ public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5 * @throws IllegalArgumentException * if {@code maxConcurrent} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable, int, int) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2654,6 +2817,19 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(int, int, ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2668,6 +2844,7 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * @throws IllegalArgumentException * if {@code maxConcurrent} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeArrayDelayError(int, int, ObservableSource...) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2686,6 +2863,19 @@ public static <T> Observable<T> mergeArray(int maxConcurrency, int bufferSize, O * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2694,6 +2884,7 @@ public static <T> Observable<T> mergeArray(int maxConcurrency, int bufferSize, O * @return an Observable that emits items that are the result of flattening the items emitted by the * ObservableSources in the Iterable * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2713,6 +2904,19 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2725,6 +2929,7 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * @throws IllegalArgumentException * if {@code maxConcurrent} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(Iterable, int) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2744,6 +2949,19 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2752,6 +2970,7 @@ public static <T> Observable<T> merge(Iterable<? extends ObservableSource<? exte * @return an Observable that emits items that are the result of flattening the ObservableSources emitted by the * {@code source} ObservableSource * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(ObservableSource) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -2773,6 +2992,19 @@ public static <T> Observable<T> merge(ObservableSource<? extends ObservableSourc * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2786,6 +3018,7 @@ public static <T> Observable<T> merge(ObservableSource<? extends ObservableSourc * if {@code maxConcurrent} is less than or equal to 0 * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> * @since 1.1.0 + * @see #mergeDelayError(ObservableSource, int) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2806,6 +3039,19 @@ public static <T> Observable<T> merge(ObservableSource<? extends ObservableSourc * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2815,6 +3061,7 @@ public static <T> Observable<T> merge(ObservableSource<? extends ObservableSourc * an ObservableSource to be merged * @return an Observable that emits all of the items emitted by the source ObservableSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(ObservableSource, ObservableSource) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2835,6 +3082,19 @@ public static <T> Observable<T> merge(ObservableSource<? extends T> source1, Obs * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2846,6 +3106,7 @@ public static <T> Observable<T> merge(ObservableSource<? extends T> source1, Obs * an ObservableSource to be merged * @return an Observable that emits all of the items emitted by the source ObservableSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(ObservableSource, ObservableSource, ObservableSource) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2867,6 +3128,19 @@ public static <T> Observable<T> merge(ObservableSource<? extends T> source1, Obs * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2880,6 +3154,7 @@ public static <T> Observable<T> merge(ObservableSource<? extends T> source1, Obs * an ObservableSource to be merged * @return an Observable that emits all of the items emitted by the source ObservableSources * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(ObservableSource, ObservableSource, ObservableSource, ObservableSource) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -2904,6 +3179,19 @@ public static <T> Observable<T> merge( * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeArray} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common element base type @@ -2911,6 +3199,7 @@ public static <T> Observable<T> merge( * the array of ObservableSources * @return an Observable that emits all of the items emitted by the ObservableSources in the Array * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeArrayDelayError(ObservableSource...) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @CheckReturnValue @@ -3341,7 +3630,7 @@ public static Observable<Integer> range(final int start, final int count) { /** * Returns an Observable that emits a sequence of Longs within a specified range. * <p> - * <img width="640" height="195" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/range.png" alt=""> + * <img width="640" height="195" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/rangeLong.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code rangeLong} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3570,7 +3859,7 @@ public static <T> Observable<T> switchOnNext(ObservableSource<? extends Observab * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the * most recently emitted of those ObservableSources and delays any exception until all ObservableSources terminate. * <p> - * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchDo.png" alt=""> + * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchOnNextDelayError.png" alt=""> * <p> * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items @@ -3603,7 +3892,7 @@ public static <T> Observable<T> switchOnNextDelayError(ObservableSource<? extend * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the * most recently emitted of those ObservableSources and delays any exception until all ObservableSources terminate. * <p> - * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchDo.png" alt=""> + * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchOnNextDelayError.png" alt=""> * <p> * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items @@ -3666,7 +3955,7 @@ public static Observable<Long> timer(long delay, TimeUnit unit) { * <img width="640" height="200" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timer.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -3693,8 +3982,8 @@ public static Observable<Long> timer(long delay, TimeUnit unit, Scheduler schedu /** * Create an Observable by wrapping an ObservableSource <em>which has to be implemented according - * to the Reactive-Streams-based Observable specification by handling - * cancellation correctly; no safeguards are provided by the Observable itself</em>. + * to the Reactive Streams based Observable specification by handling + * disposal correctly; no safeguards are provided by the Observable itself</em>. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsafeCreate} by default doesn't operate on any particular {@link Scheduler}.</dd> @@ -3706,7 +3995,6 @@ public static Observable<Long> timer(long delay, TimeUnit unit, Scheduler schedu @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public static <T> Observable<T> unsafeCreate(ObservableSource<T> onSubscribe) { - ObjectHelper.requireNonNull(onSubscribe, "source is null"); ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); if (onSubscribe instanceof Observable) { throw new IllegalArgumentException("unsafeCreate(Observable) should be upgraded"); @@ -4616,7 +4904,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Observable<R> zip( * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zipArray.png" alt=""> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zipArray.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code zipArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4677,7 +4965,7 @@ public static <T, R> Observable<R> zipArray(Function<? super Object[], ? extends * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. * * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zipIterable.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code zipIterable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4714,10 +5002,10 @@ public static <T, R> Observable<R> zipIterable(Iterable<? extends ObservableSour // *************************************************************************************************** /** - * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by the source + * Returns a Single that emits a Boolean that indicates whether all of the items emitted by the source * ObservableSource satisfy a condition. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/all.2.png" alt=""> + * <img width="640" height="264" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/all.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code all} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4740,7 +5028,7 @@ public final Single<Boolean> all(Predicate<? super T> predicate) { * Mirrors the ObservableSource (current or provided) that first either emits an item or sends a termination * notification. * <p> - * <img width="640" height="385" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png" alt=""> + * <img width="640" height="385" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ambWith.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4762,7 +5050,7 @@ public final Observable<T> ambWith(ObservableSource<? extends T> other) { } /** - * Returns an Observable that emits {@code true} if any item emitted by the source ObservableSource satisfies a + * Returns a Single that emits {@code true} if any item emitted by the source ObservableSource satisfies a * specified condition, otherwise {@code false}. <em>Note:</em> this always emits {@code false} if the * source ObservableSource is empty. * <p> @@ -4788,9 +5076,32 @@ public final Single<Boolean> any(Predicate<? super T> predicate) { return RxJavaPlugins.onAssembly(new ObservableAnySingle<T>(this, predicate)); } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * This allows fluent conversion to any other type. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code as} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current Observable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> R as(@NonNull ObservableConverter<T, ? extends R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + /** * Returns the first item emitted by this {@code Observable}, or throws * {@code NoSuchElementException} if it emits no items. + * <p> + * <img width="640" height="412" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingFirst.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4804,9 +5115,9 @@ public final Single<Boolean> any(Predicate<? super T> predicate) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final T blockingFirst() { - BlockingFirstObserver<T> s = new BlockingFirstObserver<T>(); - subscribe(s); - T v = s.blockingGet(); + BlockingFirstObserver<T> observer = new BlockingFirstObserver<T>(); + subscribe(observer); + T v = observer.blockingGet(); if (v != null) { return v; } @@ -4816,6 +5127,8 @@ public final T blockingFirst() { /** * Returns the first item emitted by this {@code Observable}, or a default value if it emits no * items. + * <p> + * <img width="640" height="329" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingFirst.o.default.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4830,30 +5143,32 @@ public final T blockingFirst() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final T blockingFirst(T defaultItem) { - BlockingFirstObserver<T> s = new BlockingFirstObserver<T>(); - subscribe(s); - T v = s.blockingGet(); + BlockingFirstObserver<T> observer = new BlockingFirstObserver<T>(); + subscribe(observer); + T v = observer.blockingGet(); return v != null ? v : defaultItem; } /** - * Invokes a method on each item emitted by this {@code Observable} and blocks until the Observable - * completes. + * Consumes the upstream {@code Observable} in a blocking fashion and invokes the given + * {@code Consumer} with each upstream item on the <em>current thread</em> until the + * upstream terminates. * <p> - * <em>Note:</em> This will block even if the underlying Observable is asynchronous. + * <img width="640" height="330" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingForEach.o.png" alt=""> * <p> - * <img width="640" height="330" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.forEach.png" alt=""> + * <em>Note:</em> the method will only return if the upstream terminates or the current + * thread is interrupted. * <p> - * This is similar to {@link Observable#subscribe(Observer)}, but it blocks. Because it blocks it does not - * need the {@link Observer#onComplete()} or {@link Observer#onError(Throwable)} methods. If the - * underlying Observable terminates with an error, rather than calling {@code onError}, this method will - * throw an exception. - * - * <p>The difference between this method and {@link #subscribe(Consumer)} is that the {@code onNext} action - * is executed on the emission thread instead of the current thread. + * This method executes the {@code Consumer} on the current thread while + * {@link #subscribe(Consumer)} executes the consumer on the original caller thread of the + * sequence. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingForEach} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param onNext @@ -4880,7 +5195,7 @@ public final void blockingForEach(Consumer<? super T> onNext) { /** * Converts this {@code Observable} into an {@link Iterable}. * <p> - * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toIterable.png" alt=""> + * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingIterable.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4898,7 +5213,7 @@ public final Iterable<T> blockingIterable() { /** * Converts this {@code Observable} into an {@link Iterable}. * <p> - * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toIterable.png" alt=""> + * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingIterable.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -4919,10 +5234,14 @@ public final Iterable<T> blockingIterable(int bufferSize) { * Returns the last item emitted by this {@code Observable}, or throws * {@code NoSuchElementException} if this {@code Observable} emits no items. * <p> - * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.last.png" alt=""> + * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingLast.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingLast} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @return the last item emitted by this {@code Observable} @@ -4933,9 +5252,9 @@ public final Iterable<T> blockingIterable(int bufferSize) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final T blockingLast() { - BlockingLastObserver<T> s = new BlockingLastObserver<T>(); - subscribe(s); - T v = s.blockingGet(); + BlockingLastObserver<T> observer = new BlockingLastObserver<T>(); + subscribe(observer); + T v = observer.blockingGet(); if (v != null) { return v; } @@ -4946,10 +5265,14 @@ public final T blockingLast() { * Returns the last item emitted by this {@code Observable}, or a default value if it emits no * items. * <p> - * <img width="640" height="310" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.lastOrDefault.png" alt=""> + * <img width="640" height="310" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingLastDefault.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingLast} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param defaultItem @@ -4961,9 +5284,9 @@ public final T blockingLast() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final T blockingLast(T defaultItem) { - BlockingLastObserver<T> s = new BlockingLastObserver<T>(); - subscribe(s); - T v = s.blockingGet(); + BlockingLastObserver<T> observer = new BlockingLastObserver<T>(); + subscribe(observer); + T v = observer.blockingGet(); return v != null ? v : defaultItem; } @@ -4971,6 +5294,8 @@ public final T blockingLast(T defaultItem) { * Returns an {@link Iterable} that returns the latest item emitted by this {@code Observable}, * waiting if necessary for one to become available. * <p> + * <img width="640" height="350" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingLatest.o.png" alt=""> + * <p> * If this {@code Observable} produces items faster than {@code Iterator.next} takes them, * {@code onNext} events might be skipped, but {@code onError} or {@code onComplete} events are not. * <p> @@ -4994,7 +5319,7 @@ public final Iterable<T> blockingLatest() { * Returns an {@link Iterable} that always returns the item most recently emitted by this * {@code Observable}. * <p> - * <img width="640" height="490" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.mostRecent.png" alt=""> + * <img width="640" height="426" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingMostRecent.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingMostRecent} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5017,7 +5342,7 @@ public final Iterable<T> blockingMostRecent(T initialValue) { * Returns an {@link Iterable} that blocks until this {@code Observable} emits another item, then * returns that item. * <p> - * <img width="640" height="490" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.next.png" alt=""> + * <img width="640" height="427" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingNext.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingNext} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5037,10 +5362,14 @@ public final Iterable<T> blockingNext() { * If this {@code Observable} completes after emitting a single item, return that item, otherwise * throw a {@code NoSuchElementException}. * <p> - * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.single.png" alt=""> + * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingSingle.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @return the single item emitted by this {@code Observable} @@ -5061,10 +5390,14 @@ public final T blockingSingle() { * more than one item, throw an {@code IllegalArgumentException}; if it emits no items, return a default * value. * <p> - * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.singleOrDefault.png" alt=""> + * <img width="640" height="315" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/blockingSingleDefault.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * * @param defaultItem @@ -5080,15 +5413,16 @@ public final T blockingSingle(T defaultItem) { } /** - * Returns a {@link Future} representing the single value emitted by this {@code Observable}. + * Returns a {@link Future} representing the only value emitted by this {@code Observable}. + * <p> + * <img width="640" height="312" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/toFuture.o.png" alt=""> * <p> * If the {@link Observable} emits more than one item, {@link java.util.concurrent.Future} will receive an - * {@link java.lang.IllegalArgumentException}. If the {@link Observable} is empty, {@link java.util.concurrent.Future} - * will receive an {@link java.util.NoSuchElementException}. + * {@link java.lang.IndexOutOfBoundsException}. If the {@link Observable} is empty, {@link java.util.concurrent.Future} + * will receive an {@link java.util.NoSuchElementException}. The {@code Observable} source has to terminate in order + * for the returned {@code Future} to terminate as well. * <p> * If the {@code Observable} may emit more than one item, use {@code Observable.toList().toFuture()}. - * <p> - * <img width="640" height="395" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toFuture.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toFuture} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5105,11 +5439,20 @@ public final Future<T> toFuture() { /** * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + * <p> + * <img width="640" height="270" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingSubscribe.o.0.png" alt=""> + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @since 2.0 + * @see #blockingSubscribe(Consumer) + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @SchedulerSupport(SchedulerSupport.NONE) public final void blockingSubscribe() { @@ -5119,15 +5462,25 @@ public final void blockingSubscribe() { /** * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. * <p> - * If the Observable emits an error, it is wrapped into an + * <img width="640" height="393" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingSubscribe.o.1.png" alt=""> + * <p> + * If the {@code Observable} emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param onNext the callback action for each source value * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @SchedulerSupport(SchedulerSupport.NONE) public final void blockingSubscribe(Consumer<? super T> onNext) { @@ -5136,6 +5489,12 @@ public final void blockingSubscribe(Consumer<? super T> onNext) { /** * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * <img width="640" height="396" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingSubscribe.o.2.png" alt=""> + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5143,15 +5502,21 @@ public final void blockingSubscribe(Consumer<? super T> onNext) { * @param onNext the callback action for each source value * @param onError the callback action for an error event * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer, Action) */ @SchedulerSupport(SchedulerSupport.NONE) public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) { ObservableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION); } - /** * Subscribes to the source and calls the given callbacks <strong>on the current thread</strong>. + * <p> + * <img width="640" height="394" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingSubscribe.o.png" alt=""> + * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5167,26 +5532,32 @@ public final void blockingSubscribe(Consumer<? super T> onNext, Consumer<? super } /** - * Subscribes to the source and calls the Observer methods <strong>on the current thread</strong>. + * Subscribes to the source and calls the {@link Observer} methods <strong>on the current thread</strong>. * <p> + * Note that calling this method will block the caller thread until the upstream terminates + * normally, with an error or the {@code Observer} disposes the {@link Disposable} it receives via + * {@link Observer#onSubscribe(Disposable)}. + * Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * The a dispose() call is composed through. - * @param subscriber the subscriber to forward events and calls to in the current thread + * @param observer the {@code Observer} instance to forward events and calls to in the current thread * @since 2.0 */ @SchedulerSupport(SchedulerSupport.NONE) - public final void blockingSubscribe(Observer<? super T> subscriber) { - ObservableBlockingSubscribe.subscribe(this, subscriber); + public final void blockingSubscribe(Observer<? super T> observer) { + ObservableBlockingSubscribe.subscribe(this, observer); } /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each containing {@code count} items. When the source - * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current buffer and - * propagates the notification from the source ObservableSource. + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer3.png" alt=""> * <dl> @@ -5209,8 +5580,9 @@ public final Observable<List<T>> buffer(int count) { /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits buffers every {@code skip} items, each containing {@code count} items. When the source - * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current buffer and - * propagates the notification from the source ObservableSource. + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer4.png" alt=""> * <dl> @@ -5237,8 +5609,9 @@ public final Observable<List<T>> buffer(int count, int skip) { /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits buffers every {@code skip} items, each containing {@code count} items. When the source - * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current buffer and - * propagates the notification from the source ObservableSource. + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer4.png" alt=""> * <dl> @@ -5272,8 +5645,9 @@ public final <U extends Collection<? super T>> Observable<U> buffer(int count, i /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each containing {@code count} items. When the source - * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current buffer and - * propagates the notification from the source ObservableSource. + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer3.png" alt=""> * <dl> @@ -5301,8 +5675,9 @@ public final <U extends Collection<? super T>> Observable<U> buffer(int count, C * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument. It emits * each buffer after a fixed timespan, specified by the {@code timespan} argument. When the source - * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current buffer and - * propagates the notification from the source ObservableSource. + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.png" alt=""> * <dl> @@ -5330,13 +5705,15 @@ public final Observable<List<T>> buffer(long timespan, long timeskip, TimeUnit u * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the - * {@code timespan} argument. When the source ObservableSource completes or encounters an error, the resulting - * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -5361,13 +5738,15 @@ public final Observable<List<T>> buffer(long timespan, long timeskip, TimeUnit u * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the - * {@code timespan} argument. When the source ObservableSource completes or encounters an error, the resulting - * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <U> the collection subclass type to buffer into @@ -5398,8 +5777,10 @@ public final <U extends Collection<? super T>> Observable<U> buffer(long timespa /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the - * {@code timespan} argument. When the source ObservableSource completes or encounters an error, the resulting - * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer5.png" alt=""> * <dl> @@ -5426,8 +5807,10 @@ public final Observable<List<T>> buffer(long timespan, TimeUnit unit) { * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached - * first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the - * current buffer and propagates the notification from the source ObservableSource. + * first). When the source ObservableSource completes, the resulting ObservableSource emits the current buffer and + * propagates the notification from the source ObservableSource. Note that if the source ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.png" alt=""> * <dl> @@ -5457,14 +5840,15 @@ public final Observable<List<T>> buffer(long timespan, TimeUnit unit, int count) * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by - * the {@code count} argument (whichever is reached first). When the source ObservableSource completes or - * encounters an error, the resulting ObservableSource emits the current buffer and propagates the notification - * from the source ObservableSource. + * the {@code count} argument (whichever is reached first). When the source ObservableSource completes, the resulting + * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. Note + * that if the source ObservableSource issues an onError notification the event is passed on immediately without + * first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -5491,14 +5875,15 @@ public final Observable<List<T>> buffer(long timespan, TimeUnit unit, Scheduler * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by - * the {@code count} argument (whichever is reached first). When the source ObservableSource completes or - * encounters an error, the resulting ObservableSource emits the current buffer and propagates the notification - * from the source ObservableSource. + * the {@code count} argument (whichever is reached first). When the source ObservableSource completes, the resulting + * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. Note + * that if the source ObservableSource issues an onError notification the event is passed on immediately without + * first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <U> the collection subclass type to buffer into @@ -5538,14 +5923,15 @@ public final <U extends Collection<? super T>> Observable<U> buffer( /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the - * {@code timespan} argument and on the specified {@code scheduler}. When the source ObservableSource completes or - * encounters an error, the resulting ObservableSource emits the current buffer and propagates the notification - * from the source ObservableSource. + * {@code timespan} argument and on the specified {@code scheduler}. When the source ObservableSource completes, + * the resulting ObservableSource emits the current buffer and propagates the notification from the source + * ObservableSource. Note that if the source ObservableSource issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer5.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -5568,7 +5954,9 @@ public final Observable<List<T>> buffer(long timespan, TimeUnit unit, Scheduler /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits buffers that it creates when the specified {@code openingIndicator} ObservableSource emits an - * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. + * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. If any of the + * source ObservableSource, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the + * event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="470" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer2.png" alt=""> * <dl> @@ -5598,7 +5986,9 @@ public final <TOpening, TClosing> Observable<List<T>> buffer( /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits buffers that it creates when the specified {@code openingIndicator} ObservableSource emits an - * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. + * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. If any of the + * source ObservableSource, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the + * event is passed on immediately without first emitting the buffer it is in the process of assembling. * <p> * <img width="640" height="470" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer2.png" alt=""> * <dl> @@ -5640,7 +6030,9 @@ public final <TOpening, TClosing, U extends Collection<? super T>> Observable<U> * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5668,7 +6060,9 @@ public final <B> Observable<List<T>> buffer(ObservableSource<B> boundary) { * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5699,7 +6093,9 @@ public final <B> Observable<List<T>> buffer(ObservableSource<B> boundary, final * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png" alt=""> * <p> * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the - * latest buffer and complete. + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5729,9 +6125,12 @@ public final <B, U extends Collection<? super T>> Observable<U> buffer(Observabl /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a - * new buffer whenever the ObservableSource produced by the specified {@code closingIndicator} emits an item. + * new buffer whenever the ObservableSource produced by the specified {@code boundarySupplier} emits an item. * <p> * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer1.png" alt=""> + * <p> + * If either the source {@code ObservableSource} or the boundary {@code ObservableSource} issues an {@code onError} notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5740,7 +6139,7 @@ public final <B, U extends Collection<? super T>> Observable<U> buffer(Observabl * @param <B> the value type of the boundary-providing ObservableSource * @param boundarySupplier * a {@link Callable} that produces an ObservableSource that governs the boundary between buffers. - * Whenever the source {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and + * Whenever the supplied {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and * begins to fill a new one * @return an Observable that emits a connected, non-overlapping buffer of items from the source ObservableSource * each time the ObservableSource created with the {@code closingIndicator} argument emits an item @@ -5750,15 +6149,17 @@ public final <B, U extends Collection<? super T>> Observable<U> buffer(Observabl @SchedulerSupport(SchedulerSupport.NONE) public final <B> Observable<List<T>> buffer(Callable<? extends ObservableSource<B>> boundarySupplier) { return buffer(boundarySupplier, ArrayListSupplier.<T>asCallable()); - } /** * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting * ObservableSource emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a - * new buffer whenever the ObservableSource produced by the specified {@code closingIndicator} emits an item. + * new buffer whenever the ObservableSource produced by the specified {@code boundarySupplier} emits an item. * <p> * <img width="640" height="395" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer1.png" alt=""> + * <p> + * If either the source {@code ObservableSource} or the boundary {@code ObservableSource} issues an {@code onError} notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5768,7 +6169,7 @@ public final <B> Observable<List<T>> buffer(Callable<? extends ObservableSource< * @param <B> the value type of the boundary-providing ObservableSource * @param boundarySupplier * a {@link Callable} that produces an ObservableSource that governs the boundary between buffers. - * Whenever the source {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and + * Whenever the supplied {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and * begins to fill a new one * @param bufferSupplier * a factory function that returns an instance of the collection subclass to be used and returned @@ -5836,14 +6237,14 @@ public final <B, U extends Collection<? super T>> Observable<U> buffer(Callable< @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable<T> cache() { - return ObservableCache.from(this); + return cacheWithInitialCapacity(16); } /** * Returns an Observable that subscribes to this ObservableSource lazily, caches all of its events * and replays them, in the same order as received, to all the downstream subscribers. * <p> - * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/cache.png" alt=""> + * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/cacheWithInitialCapacity.o.png" alt=""> * <p> * This is useful when you want an ObservableSource to cache responses and you can't control the * subscribe/dispose behavior of all the {@link Observer}s. @@ -5894,7 +6295,8 @@ public final Observable<T> cache() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable<T> cacheWithInitialCapacity(int initialCapacity) { - return ObservableCache.from(this, initialCapacity); + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return RxJavaPlugins.onAssembly(new ObservableCache<T>(this, initialCapacity)); } /** @@ -5923,12 +6325,16 @@ public final <U> Observable<U> cast(final Class<U> clazz) { } /** - * Collects items emitted by the source ObservableSource into a single mutable data structure and returns + * Collects items emitted by the finite source ObservableSource into a single mutable data structure and returns * a Single that emits this structure. * <p> * <img width="640" height="330" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collect.2.png" alt=""> * <p> * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code collect} does not operate by default on a particular {@link Scheduler}.</dd> @@ -5953,12 +6359,16 @@ public final <U> Single<U> collect(Callable<? extends U> initialValueSupplier, B } /** - * Collects items emitted by the source ObservableSource into a single mutable data structure and returns + * Collects items emitted by the finite source ObservableSource into a single mutable data structure and returns * a Single that emits this structure. * <p> - * <img width="640" height="330" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collect.2.png" alt=""> + * <img width="640" height="330" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collectInto.o.png" alt=""> * <p> * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code collectInto} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6074,7 +6484,8 @@ public final <R> Observable<R> concatMap(Function<? super T, ? extends Observabl * one at a time and emits their values in order * while delaying any error from either this or any of the inner ObservableSources * till all of them terminate. - * + * <p> + * <img width="640" height="347" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6095,7 +6506,8 @@ public final <R> Observable<R> concatMapDelayError(Function<? super T, ? extends * one at a time and emits their values in order * while delaying any error from either this or any of the inner ObservableSources * till all of them terminate. - * + * <p> + * <img width="640" height="347" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6134,6 +6546,8 @@ public final <R> Observable<R> concatMapDelayError(Function<? super T, ? extends * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in * order, each one after the previous one completes. + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapEager.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> @@ -6157,6 +6571,8 @@ public final <R> Observable<R> concatMapEager(Function<? super T, ? extends Obse * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in * order, each one after the previous one completes. + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapEager.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> @@ -6186,6 +6602,8 @@ public final <R> Observable<R> concatMapEager(Function<? super T, ? extends Obse * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in * order, each one after the previous one completes. + * <p> + * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapEagerDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> @@ -6213,6 +6631,8 @@ public final <R> Observable<R> concatMapEagerDelayError(Function<? super T, ? ex * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in * order, each one after the previous one completes. + * <p> + * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapEagerDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> @@ -6240,10 +6660,149 @@ public final <R> Observable<R> concatMapEagerDelayError(Function<? super T, ? ex return RxJavaPlugins.onAssembly(new ObservableConcatMapEager<T, R>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, maxConcurrency, prefetch)); } + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them one at a time in + * order and waits until the upstream and all CompletableSources complete. + * <p> + * <img width="640" height="505" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapCompletable.o.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.6 - experimental + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a CompletableSource + * @return a Completable that signals {@code onComplete} when the upstream and all CompletableSources complete + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletable(Function<? super T, ? extends CompletableSource> mapper) { + return concatMapCompletable(mapper, 2); + } + + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them one at a time in + * order and waits until the upstream and all CompletableSources complete. + * <p> + * <img width="640" height="505" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapCompletable.o.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.6 - experimental + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a CompletableSource + * + * @param capacityHint + * the number of upstream items expected to be buffered until the current CompletableSource, mapped from + * the current item, completes. + * @return a Completable that signals {@code onComplete} when the upstream and all CompletableSources complete + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletable(Function<? super T, ? extends CompletableSource> mapper, int capacityHint) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapCompletable<T>(this, mapper, ErrorMode.IMMEDIATE, capacityHint)); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper) { + return concatMapCompletableDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @return a new Completable instance + * @see #concatMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper, boolean tillTheEnd) { + return concatMapCompletableDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function<? super T, ? extends CompletableSource> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapCompletable<T>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + /** * Returns an Observable that concatenate each item emitted by the source ObservableSource with the values in an * Iterable corresponding to that item that is generated by a selector. * <p> + * <img width="640" height="275" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapIterable.o.png" alt=""> * * <dl> * <dt><b>Scheduler:</b></dt> @@ -6270,6 +6829,7 @@ public final <U> Observable<U> concatMapIterable(final Function<? super T, ? ext * Returns an Observable that concatenate each item emitted by the source ObservableSource with the values in an * Iterable corresponding to that item that is generated by a selector. * <p> + * <img width="640" height="275" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapIterable.o.png" alt=""> * * <dl> * <dt><b>Scheduler:</b></dt> @@ -6296,33 +6856,402 @@ public final <U> Observable<U> concatMapIterable(final Function<? super T, ? ext } /** - * Returns an Observable that emits the items emitted from the current ObservableSource, then the next, one after - * the other, without interleaving them. + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Observable} or the current inner {@code MaybeSource} fail. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * <dd>{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * - * @param other - * an ObservableSource to be concatenated after the current - * @return an Observable that emits items emitted by the two source ObservableSources, one after the other, - * without interleaving them - * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapMaybeDelayError(Function) + * @see #concatMapMaybe(Function, int) + * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - public final Observable<T> concatWith(ObservableSource<? extends T> other) { - ObjectHelper.requireNonNull(other, "other is null"); - return concat(this, other); + public final <R> Observable<R> concatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + return concatMapMaybe(mapper, 2); } /** - * Returns an Observable that emits a Boolean that indicates whether the source ObservableSource emitted a - * specified item. + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Observable} or the current inner {@code MaybeSource} fail. * <p> - * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/contains.2.png" alt=""> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Observable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapMaybe(Function<? super T, ? extends MaybeSource<? extends R>> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapMaybe<T, R>(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + return concatMapMaybeDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @return a new Observable instance + * @see #concatMapMaybe(Function, int) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean tillTheEnd) { + return concatMapMaybeDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Observable instance + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapMaybeDelayError(Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapMaybe<T, R>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Observable} or the current inner {@code SingleSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapSingleDelayError(Function) + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapSingle(Function<? super T, ? extends SingleSource<? extends R>> mapper) { + return concatMapSingle(mapper, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Observable} or the current inner {@code SingleSource} fail. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Observable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapSingle(Function<? super T, ? extends SingleSource<? extends R>> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapSingle<T, R>(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and delays all errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper) { + return concatMapSingleDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays all errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @return a new Observable instance + * @see #concatMapSingle(Function, int) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean tillTheEnd) { + return concatMapSingleDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Observable instance + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> concatMapSingleDelayError(Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapSingle<T, R>(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Returns an Observable that emits the items emitted from the current ObservableSource, then the next, one after + * the other, without interleaving them. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @param other + * an ObservableSource to be concatenated after the current + * @return an Observable that emits items emitted by the two source ObservableSources, one after the other, + * without interleaving them + * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> concatWith(ObservableSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concat(this, other); + } + + /** + * Returns an {@code Observable} that emits the items from this {@code Observable} followed by the success item or error event + * of the other {@link SingleSource}. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the SingleSource whose signal should be emitted after this {@code Observable} completes normally. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> concatWith(@NonNull SingleSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithSingle<T>(this, other)); + } + + /** + * Returns an {@code Observable} that emits the items from this {@code Observable} followed by the success item or terminal events + * of the other {@link MaybeSource}. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the MaybeSource whose signal should be emitted after this Observable completes normally. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> concatWith(@NonNull MaybeSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithMaybe<T>(this, other)); + } + + /** + * Returns an {@code Observable} that emits items from this {@code Observable} and when it completes normally, the + * other {@link CompletableSource} is subscribed to and the returned {@code Observable} emits its terminal events. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code concatWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to subscribe to once the current {@code Observable} completes normally + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> concatWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithCompletable<T>(this, other)); + } + + /** + * Returns a Single that emits a Boolean that indicates whether the source ObservableSource emitted a + * specified item. + * <p> + * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/contains.2.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code contains} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6354,7 +7283,6 @@ public final Single<Boolean> contains(final Object element) { * @return a Single that emits a single item: the number of items emitted by the source ObservableSource as a * 64-bit Long item * @see <a href="http://reactivex.io/documentation/operators/count.html">ReactiveX operators documentation: Count</a> - * @see #count() */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -6367,6 +7295,14 @@ public final Single<Long> count() { * source ObservableSource that are followed by another item within a computed debounce duration. * <p> * <img width="640" height="425" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.f.png" alt=""> + * <p> + * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code ObservableSource} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6397,23 +7333,23 @@ public final <U> Observable<T> debounce(Function<? super T, ? extends Observable * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.png" alt=""> * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>This version of {@code debounce} operates by default on the {@code computation} {@link Scheduler}.</dd> + * <dd>{@code debounce} operates by default on the {@code computation} {@link Scheduler}.</dd> * </dl> * * @param timeout - * the time each item has to be "the most recent" of those emitted by the source ObservableSource to - * ensure that it's not dropped + * the length of the window of time that must pass after the emission of an item from the source + * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the + * resulting ObservableSource * @param unit - * the {@link TimeUnit} for the timeout + * the unit of time for the specified {@code timeout} * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> @@ -6435,23 +7371,22 @@ public final Observable<T> debounce(long timeout, TimeUnit unit) { * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.s.png" alt=""> * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout * the time each item has to be "the most recent" of those emitted by the source ObservableSource to * ensure that it's not dropped * @param unit - * the unit of time for the specified timeout + * the unit of time for the specified {@code timeout} * @param scheduler * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each * item @@ -6546,7 +7481,7 @@ public final Observable<T> delay(long delay, TimeUnit unit) { /** * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a - * specified delay. Error notifications from the source ObservableSource are not delayed. + * specified delay. If {@code delayError} is true, error notifications will also be delayed. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.png" alt=""> * <dl> @@ -6577,7 +7512,7 @@ public final Observable<T> delay(long delay, TimeUnit unit, boolean delayError) * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -6597,12 +7532,12 @@ public final Observable<T> delay(long delay, TimeUnit unit, Scheduler scheduler) /** * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a - * specified delay. Error notifications from the source ObservableSource are not delayed. + * specified delay. If {@code delayError} is true, error notifications will also be delayed. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -6714,7 +7649,7 @@ public final Observable<T> delaySubscription(long delay, TimeUnit unit) { * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delaySubscription.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param delay @@ -6739,6 +7674,27 @@ public final Observable<T> delaySubscription(long delay, TimeUnit unit, Schedule * represent. * <p> * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/dematerialize.png" alt=""> + * <p> + * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Observable disposes of the flow and terminates with that type of terminal event: + * <pre><code> + * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2)) + * .doOnDispose(() -> System.out.println("Disposed!")); + * .dematerialize() + * .test() + * .assertResult(1); + * </code></pre> + * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + * <pre><code> + * Observable.just(createOnNext(1), createOnNext(2)) + * .dematerialize() + * .test() + * .assertResult(1, 2); + * </code></pre> + * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(ObservableSource)} + * with a {@link #never()} source. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code dematerialize} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6748,19 +7704,91 @@ public final Observable<T> delaySubscription(long delay, TimeUnit unit, Schedule * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects * emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Dematerialize</a> + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) public final <T2> Observable<T2> dematerialize() { - @SuppressWarnings("unchecked") - Observable<Notification<T2>> m = (Observable<Notification<T2>>)this; - return RxJavaPlugins.onAssembly(new ObservableDematerialize<T2>(m)); + return RxJavaPlugins.onAssembly(new ObservableDematerialize(this, Functions.identity())); + } + + /** + * Returns an Observable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Observer} signal types. + * <p> + * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/dematerialize.png" alt=""> + * <p> + * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification<T>}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + * <p> + * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Observable disposes of the flow and terminates with that type of terminal event: + * <pre><code> + * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2)) + * .doOnDispose(() -> System.out.println("Disposed!")); + * .dematerialize(notification -> notification) + * .test() + * .assertResult(1); + * </code></pre> + * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + * <pre><code> + * Observable.just(createOnNext(1), createOnNext(2)) + * .dematerialize(notification -> notification) + * .test() + * .assertResult(1, 2); + * </code></pre> + * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(ObservableSource)} + * with a {@link #never()} source. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code dematerialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @param <R> the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Observer} event to the downstream. + * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source ObservableSource + * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Dematerialize</a> + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> dematerialize(Function<? super T, Notification<R>> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new ObservableDematerialize<T, R>(this, selector)); } /** - * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct. + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct + * based on {@link Object#equals(Object)} comparison. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.png" alt=""> + * <p> + * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between items as the default Java + * implementation only considers reference equivalence. + * <p> + * By default, {@code distinct()} uses an internal {@link java.util.HashSet} per Observer to remember + * previously seen items and uses {@link java.util.Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + * <p> + * Note that this internal {@code HashSet} may grow unbounded as items won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct elements) may lead + * to {@code OutOfMemoryError}. + * <p> + * Customizing the retention policy can happen only by providing a custom {@link java.util.Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinct} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6769,6 +7797,8 @@ public final <T2> Observable<T2> dematerialize() { * @return an Observable that emits only those items emitted by the source ObservableSource that are distinct from * each other * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinct(Function) + * @see #distinct(Function, Callable) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -6778,9 +7808,25 @@ public final Observable<T> distinct() { /** * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct according - * to a key selector function. + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between the key objects as the default + * Java implementation only considers reference equivalence. + * <p> + * By default, {@code distinct()} uses an internal {@link java.util.HashSet} per Observer to remember + * previously seen keys and uses {@link java.util.Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + * <p> + * Note that this internal {@code HashSet} may grow unbounded as keys won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct keys) may lead + * to {@code OutOfMemoryError}. + * <p> + * Customizing the retention policy can happen only by providing a custom {@link java.util.Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinct} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6792,6 +7838,7 @@ public final Observable<T> distinct() { * is distinct from another one or not * @return an Observable that emits those items emitted by the source ObservableSource that have distinct keys * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinct(Function, Callable) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -6801,9 +7848,14 @@ public final <K> Observable<T> distinct(Function<? super T, K> keySelector) { /** * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct according - * to a key selector function. + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between the key objects as + * the default Java implementation only considers reference equivalence. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinct} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6829,9 +7881,25 @@ public final <K> Observable<T> distinct(Function<? super T, K> keySelector, Call /** * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct from their - * immediate predecessors. + * immediate predecessors based on {@link Object#equals(Object)} comparison. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.png" alt=""> + * <p> + * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} to provide + * meaningful comparison between items as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code T} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the class {@code T}. + * <p> + * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6840,6 +7908,7 @@ public final <K> Observable<T> distinct(Function<? super T, K> keySelector, Call * @return an Observable that emits those items from the source ObservableSource that are distinct from their * immediate predecessors * @see <a href="http://reactivex.io/documentation/operators/distinct.html">ReactiveX operators documentation: Distinct</a> + * @see #distinctUntilChanged(BiPredicate) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -6849,9 +7918,27 @@ public final Observable<T> distinctUntilChanged() { /** * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct from their - * immediate predecessors, according to a key selector function. + * immediate predecessors, according to a key selector function and based on {@link Object#equals(Object)} comparison + * of those objects returned by the key selector function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.key.png" alt=""> + * <p> + * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} to provide + * meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code K} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the item class {@code T} (for which the keys can be + * derived via a similar selector). + * <p> + * Note that the operator always retains the latest key from upstream regardless of the comparison result + * and uses it in the next comparison with the next key derived from the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6877,6 +7964,16 @@ public final <K> Observable<T> distinctUntilChanged(Function<? super T, K> keySe * immediate predecessors when compared with each other via the provided comparator function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.png" alt=""> + * <p> + * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + * <p> + * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6900,6 +7997,8 @@ public final Observable<T> distinctUntilChanged(BiPredicate<? super T, ? super T * Calls the specified consumer with the current item after this item has been emitted to the downstream. * <p>Note that the {@code onAfterNext} action is shared between subscriptions and as such * should be thread-safe. + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doAfterNext.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doAfterNext} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6949,6 +8048,8 @@ public final Observable<T> doAfterTerminate(Action onFinally) { * is executed once per subscription. * <p>Note that the {@code onFinally} action is shared between subscriptions and as such * should be thread-safe. + * <p> + * <img width="640" height="281" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doFinally.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doFinally} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6956,7 +8057,7 @@ public final Observable<T> doAfterTerminate(Action onFinally) { * <dd>This operator supports boundary-limited synchronous or asynchronous queue-fusion.</dd> * </dl> * <p>History: 2.0.1 - experimental - * @param onFinally the action called when this Observable terminates or gets cancelled + * @param onFinally the action called when this Observable terminates or gets disposed * @return the new Observable instance * @since 2.1 */ @@ -6976,7 +8077,7 @@ public final Observable<T> doFinally(Action onFinally) { * If the action throws a runtime exception, that exception is rethrown by the {@code dispose()} call, * sometimes as a {@code CompositeException} if there were multiple exceptions along the way. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnUnsubscribe.png" alt=""> + * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnDispose.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.</dd> @@ -6997,7 +8098,7 @@ public final Observable<T> doOnDispose(Action onDispose) { /** * Modifies the source ObservableSource so that it invokes an action when it calls {@code onComplete}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnComplete.png" alt=""> + * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnComplete.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7054,7 +8155,7 @@ private Observable<T> doOnEach(Consumer<? super T> onNext, Consumer<? super Thro @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable<T> doOnEach(final Consumer<? super Notification<T>> onNotification) { - ObjectHelper.requireNonNull(onNotification, "consumer is null"); + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); return doOnEach( Functions.notificationOnNext(onNotification), Functions.notificationOnError(onNotification), @@ -7100,7 +8201,7 @@ public final Observable<T> doOnEach(final Observer<? super T> observer) { * In case the {@code onError} action throws, the downstream will receive a composite exception containing * the original exception and the exception thrown by {@code onError}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnError.png" alt=""> + * <img width="640" height="355" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7119,9 +8220,9 @@ public final Observable<T> doOnError(Consumer<? super Throwable> onError) { /** * Calls the appropriate onXXX method (shared between all Observer) for the lifecycle events of - * the sequence (subscription, cancellation, requesting). + * the sequence (subscription, disposal). * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnLifecycle.png" alt=""> + * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnLifecycle.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnLifecycle} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7145,7 +8246,7 @@ public final Observable<T> doOnLifecycle(final Consumer<? super Disposable> onSu /** * Modifies the source ObservableSource so that it invokes an action when it calls {@code onNext}. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnNext.png" alt=""> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnNext.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnNext} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7189,7 +8290,7 @@ public final Observable<T> doOnSubscribe(Consumer<? super Disposable> onSubscrib * Modifies the source ObservableSource so that it invokes an action when it calls {@code onComplete} or * {@code onError}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnTerminate.png" alt=""> + * <img width="640" height="327" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnTerminate.o.png" alt=""> * <p> * This differs from {@code doAfterTerminate} in that this happens <em>before</em> the {@code onComplete} or * {@code onError} notification. @@ -7217,7 +8318,7 @@ public final Observable<T> doOnTerminate(final Action onTerminate) { * Returns a Maybe that emits the single item at a specified index in a sequence of emissions from * this Observable or completes if this Observable signals fewer elements than index. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAt.2m.png" alt=""> + * <img width="640" height="363" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAt.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code elementAt} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7244,7 +8345,7 @@ public final Maybe<T> elementAt(long index) { * Returns a Single that emits the item found at a specified index in a sequence of emissions from * this Observable, or a default item if that index is out of range. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAt.2s.png" alt=""> + * <img width="640" height="353" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAtDefault.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code elementAt} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7274,7 +8375,7 @@ public final Single<T> elementAt(long index, T defaultItem) { * Returns a Single that emits the item found at a specified index in a sequence of emissions from this Observable * or signals a {@link NoSuchElementException} if this Observable signals fewer elements than index. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAt.2s.png" alt=""> + * <img width="640" height="362" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAtOrError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code elementAtOrError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7324,7 +8425,7 @@ public final Observable<T> filter(Predicate<? super T> predicate) { * Returns a Maybe that emits only the very first item emitted by the source ObservableSource, or * completes if the source ObservableSource is empty. * <p> - * <img width="640" height="237" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/firstElement.m.png" alt=""> + * <img width="640" height="286" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/firstElement.m.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code firstElement} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7364,7 +8465,7 @@ public final Single<T> first(T defaultItem) { * Returns a Single that emits only the very first item emitted by this Observable or * signals a {@link NoSuchElementException} if this Observable is empty. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/first.2.png" alt=""> + * <img width="640" height="434" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/firstOrError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code firstOrError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7410,7 +8511,7 @@ public final <R> Observable<R> flatMap(Function<? super T, ? extends ObservableS * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting * ObservableSources and emitting the results of this merger. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> + * <img width="640" height="356" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7440,7 +8541,7 @@ public final <R> Observable<R> flatMap(Function<? super T, ? extends ObservableS * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> + * <img width="640" height="441" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapMaxConcurrency.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7473,7 +8574,7 @@ public final <R> Observable<R> flatMap(Function<? super T, ? extends ObservableS * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> + * <img width="640" height="441" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapMaxConcurrency.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7555,7 +8656,7 @@ public final <R> Observable<R> flatMap( * ObservableSource and then flattens the ObservableSources returned from these functions and emits the resulting items, * while limiting the maximum number of concurrent subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.nce.png" alt=""> --> + * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.nce.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7597,7 +8698,7 @@ public final <R> Observable<R> flatMap( * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png" alt=""> --> + * <img width="640" height="441" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapMaxConcurrency.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7689,7 +8790,7 @@ public final <U, R> Observable<R> flatMap(Function<? super T, ? extends Observab * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> + * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7726,7 +8827,7 @@ public final <U, R> Observable<R> flatMap(Function<? super T, ? extends Observab * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> + * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7767,7 +8868,7 @@ public final <U, R> Observable<R> flatMap(final Function<? super T, ? extends Ob * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. * <p> - * <!-- <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> --> + * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeMap.r.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7799,6 +8900,8 @@ public final <U, R> Observable<R> flatMap(Function<? super T, ? extends Observab /** * Maps each element of the upstream Observable into CompletableSources, subscribes to them and * waits until the upstream and all CompletableSources complete. + * <p> + * <img width="640" height="424" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapCompletable.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7815,6 +8918,8 @@ public final Completable flatMapCompletable(Function<? super T, ? extends Comple /** * Maps each element of the upstream Observable into CompletableSources, subscribes to them and * waits until the upstream and all CompletableSources complete, optionally delaying all errors. + * <p> + * <img width="640" height="361" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapCompletableDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7835,7 +8940,7 @@ public final Completable flatMapCompletable(Function<? super T, ? extends Comple * Returns an Observable that merges each item emitted by the source ObservableSource with the values in an * Iterable corresponding to that item that is generated by a selector. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapIterable.o.png" alt=""> + * <img width="640" height="343" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapIterable.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7861,7 +8966,7 @@ public final <U> Observable<U> flatMapIterable(final Function<? super T, ? exten * Returns an Observable that emits the results of applying a function to the pair of values from the source * ObservableSource and an Iterable corresponding to that item that is generated by a selector. * <p> - * <img width="640" height="390" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapIterable.o.r.png" alt=""> + * <img width="640" height="410" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapIterable.o.r.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -7891,8 +8996,8 @@ public final <U, V> Observable<V> flatMapIterable(final Function<? super T, ? ex } /** - * Maps each element of the upstream Observable into MaybeSources, subscribes to them and - * waits until the upstream and all MaybeSources complete. + * Maps each element of the upstream Observable into MaybeSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Observable sequence. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapMaybe.png" alt=""> * <dl> @@ -7910,8 +9015,9 @@ public final <R> Observable<R> flatMapMaybe(Function<? super T, ? extends MaybeS } /** - * Maps each element of the upstream Observable into MaybeSources, subscribes to them and - * waits until the upstream and all MaybeSources complete, optionally delaying all errors. + * Maps each element of the upstream Observable into MaybeSources, subscribes to them + * and merges their onSuccess values, in no particular order, into a single Observable sequence, + * optionally delaying all errors. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapMaybe.png" alt=""> * <dl> @@ -7932,8 +9038,8 @@ public final <R> Observable<R> flatMapMaybe(Function<? super T, ? extends MaybeS } /** - * Maps each element of the upstream Observable into SingleSources, subscribes to them and - * waits until the upstream and all SingleSources complete. + * Maps each element of the upstream Observable into SingleSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Observable sequence. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapSingle.png" alt=""> * <dl> @@ -7951,8 +9057,9 @@ public final <R> Observable<R> flatMapSingle(Function<? super T, ? extends Singl } /** - * Maps each element of the upstream Observable into SingleSources, subscribes to them and - * waits until the upstream and all SingleSources complete, optionally delaying all errors. + * Maps each element of the upstream Observable into SingleSources, subscribes to them + * and merges their onSuccess values, in no particular order, into a single Observable sequence, + * optionally delaying all errors. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapSingle.png" alt=""> * <dl> @@ -7975,6 +9082,8 @@ public final <R> Observable<R> flatMapSingle(Function<? super T, ? extends Singl /** * Subscribes to the {@link ObservableSource} and receives notifications for each element. * <p> + * <img width="640" height="264" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/forEach.o.png" alt=""> + * <p> * Alias to {@link #subscribe(Consumer)} * <dl> * <dt><b>Scheduler:</b></dt> @@ -7984,7 +9093,7 @@ public final <R> Observable<R> flatMapSingle(Function<? super T, ? extends Singl * @param onNext * {@link Consumer} to execute for each item. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> @@ -7999,6 +9108,8 @@ public final Disposable forEach(Consumer<? super T> onNext) { * Subscribes to the {@link ObservableSource} and receives notifications for each element until the * onNext Predicate returns false. * <p> + * <img width="640" height="272" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/forEachWhile.o.png" alt=""> + * <p> * If the Observable emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -8010,7 +9121,7 @@ public final Disposable forEach(Consumer<? super T> onNext) { * @param onNext * {@link Predicate} to execute for each item. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> @@ -8034,7 +9145,7 @@ public final Disposable forEachWhile(Predicate<? super T> onNext) { * @param onError * {@link Consumer} to execute when an error is emitted. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null @@ -8061,7 +9172,7 @@ public final Disposable forEachWhile(Predicate<? super T> onNext, Consumer<? sup * @param onComplete * {@link Action} to execute when completion is signalled. * @return - * a Disposable that allows cancelling an asynchronous sequence + * a Disposable that allows disposing of an asynchronous sequence * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null, or @@ -8331,6 +9442,8 @@ public final <TRight, TLeftEnd, TRightEnd, R> Observable<R> groupJoin( * <p>Allows hiding extra features such as {@link io.reactivex.subjects.Subject}'s * {@link Observer} methods or preventing certain identity-based * optimizations (fusion). + * <p> + * <img width="640" height="283" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/hide.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code hide} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8364,7 +9477,7 @@ public final Completable ignoreElements() { } /** - * Returns an Observable that emits {@code true} if the source ObservableSource is empty, otherwise {@code false}. + * Returns a Single that emits {@code true} if the source ObservableSource is empty, otherwise {@code false}. * <p> * In Rx.Net this is negated as the {@code any} Observer but we renamed this in RxJava to better match Java * naming idioms. @@ -8463,7 +9576,7 @@ public final Maybe<T> lastElement() { * * @param defaultItem * the default item to emit if the source ObservableSource is empty - * @return an Observable that emits only the last item emitted by the source ObservableSource, or a default item + * @return a Single that emits only the last item emitted by the source ObservableSource, or a default item * if the source ObservableSource is empty * @see <a href="http://reactivex.io/documentation/operators/last.html">ReactiveX operators documentation: Last</a> */ @@ -8478,7 +9591,7 @@ public final Single<T> last(T defaultItem) { * Returns a Single that emits only the last item emitted by this Observable or * signals a {@link NoSuchElementException} if this Observable is empty. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/last.2.png" alt=""> + * <img width="640" height="236" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/lastOrError.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code lastOrError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8495,35 +9608,151 @@ public final Single<T> lastOrError() { } /** - * <strong>This method requires advanced knowledge about building operators; please consider + * <strong>This method requires advanced knowledge about building operators, please consider * other standard composition methods first;</strong> - * Lifts a function to the current ObservableSource and returns a new ObservableSource that when subscribed to will pass - * the values of the current ObservableSource through the Operator function. + * Returns an {@code Observable} which, when subscribed to, invokes the {@link ObservableOperator#apply(Observer) apply(Observer)} method + * of the provided {@link ObservableOperator} for each individual downstream {@link Observer} and allows the + * insertion of a custom operator by accessing the downstream's {@link Observer} during this subscription phase + * and providing a new {@code Observer}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + * <p> + * Generally, such a new {@code Observer} will wrap the downstream's {@code Observer} and forwards the + * {@code onNext}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. * <p> - * In other words, this allows chaining Observers together on an ObservableSource for acting on the values within - * the ObservableSource. - * <p> {@code - * ObservableSource.map(...).filter(...).take(5).lift(new OperatorA()).lift(new OperatorB(...)).subscribe() + * Example: + * <pre><code> + * // Step 1: Create the consumer type that will be returned by the ObservableOperator.apply(): + * + * public final class CustomObserver<T> implements Observer<T>, Disposable { + * + * // The downstream's Observer that will receive the onXXX events + * final Observer<? super String> downstream; + * + * // The connection to the upstream source that will call this class' onXXX methods + * Disposable upstream; + * + * // The constructor takes the downstream subscriber and usually any other parameters + * public CustomObserver(Observer<? super String> downstream) { + * this.downstream = downstream; + * } + * + * // In the subscription phase, the upstream sends a Disposable to this class + * // and subsequently this class has to send a Disposable to the downstream. + * // Note that relaying the upstream's Disposable directly is not allowed in RxJava + * @Override + * public void onSubscribe(Disposable d) { + * if (upstream != null) { + * d.dispose(); + * } else { + * upstream = d; + * downstream.onSubscribe(this); + * } + * } + * + * // The upstream calls this with the next item and the implementation's + * // responsibility is to emit an item to the downstream based on the intended + * // business logic, or if it can't do so for the particular item, + * // request more from the upstream + * @Override + * public void onNext(T item) { + * String str = item.toString(); + * if (str.length() < 2) { + * downstream.onNext(str); + * } + * // Observable doesn't support backpressure, therefore, there is no + * // need or opportunity to call upstream.request(1) if an item + * // is not produced to the downstream + * } + * + * // Some operators may handle the upstream's error while others + * // could just forward it to the downstream. + * @Override + * public void onError(Throwable throwable) { + * downstream.onError(throwable); + * } + * + * // When the upstream completes, usually the downstream should complete as well. + * @Override + * public void onComplete() { + * downstream.onComplete(); + * } + * + * // Some operators may use their own resources which should be cleaned up if + * // the downstream disposes the flow before it completed. Operators without + * // resources can simply forward the dispose to the upstream. + * // In some cases, a disposed flag may be set by this method so that other parts + * // of this class may detect the dispose and stop sending events + * // to the downstream. + * @Override + * public void dispose() { + * upstream.dispose(); + * } + * + * // Some operators may simply forward the call to the upstream while others + * // can return the disposed flag set in dispose(). + * @Override + * public boolean isDisposed() { + * return upstream.isDisposed(); + * } + * } + * + * // Step 2: Create a class that implements the ObservableOperator interface and + * // returns the custom consumer type from above in its apply() method. + * // Such class may define additional parameters to be submitted to + * // the custom consumer type. + * + * final class CustomOperator<T> implements ObservableOperator<String, T> { + * @Override + * public Observer<T> apply(Observer<? super String> downstream) { + * return new CustomObserver<T>(downstream); + * } * } + * + * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it + * // or reusing an existing one. + * + * Observable.range(5, 10) + * .lift(new CustomOperator<Integer>()) + * .test() + * .assertResult("5", "6", "7", "8", "9"); + * </code></pre> * <p> - * If the operator you are creating is designed to act on the individual items emitted by a source - * ObservableSource, use {@code lift}. If your operator is designed to transform the source ObservableSource as a whole - * (for instance, by applying a particular set of existing RxJava operators to it) use {@link #compose}. + * Creating custom operators can be complicated and it is recommended one consults the + * <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + * <p> + * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Observable} + * class and creating an {@link ObservableTransformer} with it is recommended. + * <p> + * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code Observer} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Observable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return an {@code Observer} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using an {@code ObservableTransformer} and extending the {@code Observable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}.</dd> + * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link ObservableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.</dd> * </dl> * * @param <R> the output value type - * @param lifter the Operator that implements the ObservableSource-operating function to be applied to the source - * ObservableSource - * @return an Observable that is the result of applying the lifted Operator to the source ObservableSource - * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators">RxJava wiki: Implementing Your Own Operators</a> + * @param lifter the {@link ObservableOperator} that receives the downstream's {@code Observer} and should return + * an {@code Observer} with custom behavior to be used as the consumer for the current + * {@code Observable}. + * @return the new Observable instance + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> + * @see #compose(ObservableTransformer) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final <R> Observable<R> lift(ObservableOperator<? extends R, ? super T> lifter) { - ObjectHelper.requireNonNull(lifter, "onLift is null"); + ObjectHelper.requireNonNull(lifter, "lifter is null"); return RxJavaPlugins.onAssembly(new ObservableLift<R, T>(this, lifter)); } @@ -8564,35 +9793,104 @@ public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) { * @return an Observable that emits items that are the result of materializing the items and notifications * of the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/materialize-dematerialize.html">ReactiveX operators documentation: Materialize</a> + * @see #dematerialize(Function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<Notification<T>> materialize() { + return RxJavaPlugins.onAssembly(new ObservableMaterialize<T>(this)); + } + + /** + * Flattens this and another ObservableSource into a single ObservableSource, without any transformation. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <p> + * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code mergeWith} method. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @param other + * an ObservableSource to be merged + * @return an Observable that emits all of the items emitted by the source ObservableSources + * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> mergeWith(ObservableSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return merge(this, other); + } + + /** + * Merges the sequence of items of this Observable with the success value of the other SingleSource. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <p> + * The success value of the other {@code SingleSource} can get interleaved at any point of this + * {@code Observable} sequence. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code SingleSource} whose success value to merge with + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> mergeWith(@NonNull SingleSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableMergeWithSingle<T>(this, other)); + } + + /** + * Merges the sequence of items of this Observable with the success value of the other MaybeSource + * or waits both to complete normally if the MaybeSource is empty. + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> + * <p> + * The success value of the other {@code MaybeSource} can get interleaved at any point of this + * {@code Observable} sequence. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.10 - experimental + * @param other the {@code MaybeSource} which provides a success value to merge with or completes + * @return the new Observable instance + * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - public final Observable<Notification<T>> materialize() { - return RxJavaPlugins.onAssembly(new ObservableMaterialize<T>(this)); + public final Observable<T> mergeWith(@NonNull MaybeSource<? extends T> other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableMergeWithMaybe<T>(this, other)); } /** - * Flattens this and another ObservableSource into a single ObservableSource, without any transformation. + * Relays the items of this Observable and completes only when the other CompletableSource completes + * as well. * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png" alt=""> - * <p> - * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by - * using the {@code mergeWith} method. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code mergeWith} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * - * @param other - * an ObservableSource to be merged - * @return an Observable that emits all of the items emitted by the source ObservableSources - * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * <p>History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to await for completion + * @return the new Observable instance + * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - public final Observable<T> mergeWith(ObservableSource<? extends T> other) { + public final Observable<T> mergeWith(@NonNull CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); - return merge(this, other); + return RxJavaPlugins.onAssembly(new ObservableMergeWithCompletable<T>(this, other)); } /** @@ -8603,9 +9901,14 @@ public final Observable<T> mergeWith(ObservableSource<? extends T> other) { * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * <p>"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed * on the other side of the asynchronous boundary. @@ -8619,6 +9922,7 @@ public final Observable<T> mergeWith(ObservableSource<? extends T> other) { * @see #subscribeOn * @see #observeOn(Scheduler, boolean) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -8631,9 +9935,14 @@ public final Observable<T> observeOn(Scheduler scheduler) { * asynchronously with an unbounded buffer with {@link Flowable#bufferSize()} "island size" and optionally delays onError notifications. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * <p>"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed * on the other side of the asynchronous boundary. @@ -8651,6 +9960,7 @@ public final Observable<T> observeOn(Scheduler scheduler) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -8663,9 +9973,14 @@ public final Observable<T> observeOn(Scheduler scheduler, boolean delayError) { * asynchronously with an unbounded buffer of configurable "island size" and optionally delays onError notifications. * <p> * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/observeOn.png" alt=""> + * <p> + * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * <p>"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed * on the other side of the asynchronous boundary. Values below 16 are not recommended in performance sensitive scenarios. @@ -8684,6 +9999,7 @@ public final Observable<T> observeOn(Scheduler scheduler, boolean delayError) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -8791,7 +10107,7 @@ public final Observable<T> onErrorResumeNext(final ObservableSource<? extends T> * Instructs an ObservableSource to emit an item (returned by a specified function) rather than invoking * {@link Observer#onError onError} if it encounters an error. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorReturn.png" alt=""> + * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorReturn.o.png" alt=""> * <p> * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits @@ -8824,7 +10140,7 @@ public final Observable<T> onErrorReturn(Function<? super Throwable, ? extends T * Instructs an ObservableSource to emit an item (returned by a specified function) rather than invoking * {@link Observer#onError onError} if it encounters an error. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorReturn.png" alt=""> + * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorReturnItem.o.png" alt=""> * <p> * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits @@ -8895,11 +10211,13 @@ public final Observable<T> onExceptionResumeNext(final ObservableSource<? extend /** * Nulls out references to the upstream producer and downstream Observer if * the sequence is terminated or downstream calls dispose(). + * <p> + * <img width="640" height="246" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onTerminateDetach.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @return an Observable which out references to the upstream producer and downstream Observer if + * @return an Observable which nulls out references to the upstream producer and downstream Observer if * the sequence is terminated or downstream calls dispose() * @since 2.0 */ @@ -8934,7 +10252,7 @@ public final ConnectableObservable<T> publish() { * Returns an Observable that emits the results of invoking a specified selector on items emitted by a * {@link ConnectableObservable} that shares a single subscription to the underlying sequence. * <p> - * <img width="640" height="510" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishConnect.f.png" alt=""> + * <img width="640" height="647" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishFunction.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code publish} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8959,7 +10277,7 @@ public final <R> Observable<R> publish(Function<? super Observable<T>, ? extends /** * Returns a Maybe that applies a specified accumulator function to the first item emitted by a source * ObservableSource, then feeds the result of that function along with the second item emitted by the source - * ObservableSource into the same function, and so on until all items have been emitted by the source ObservableSource, + * ObservableSource into the same function, and so on until all items have been emitted by the finite source ObservableSource, * and emits the final result from the final call to your function as its sole item. * <p> * <img width="640" height="320" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduce.2.png" alt=""> @@ -8967,6 +10285,10 @@ public final <R> Observable<R> publish(Function<? super Observable<T>, ? extends * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code reduce} does not operate by default on a particular {@link Scheduler}.</dd> @@ -8991,9 +10313,9 @@ public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { * Returns a Single that applies a specified accumulator function to the first item emitted by a source * ObservableSource and a specified seed value, then feeds the result of that function along with the second item * emitted by an ObservableSource into the same function, and so on until all items have been emitted by the - * source ObservableSource, emitting the final result from the final call to your function as its sole item. + * finite source ObservableSource, emitting the final result from the final call to your function as its sole item. * <p> - * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceSeed.2.png" alt=""> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceSeed.o.png" alt=""> * <p> * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method @@ -9016,6 +10338,10 @@ public final Maybe<T> reduce(BiFunction<T, T, T> reducer) { * * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item))); * </code></pre> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code reduce} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9045,14 +10371,18 @@ public final <R> Single<R> reduce(R seed, BiFunction<R, ? super T, R> reducer) { * Returns a Single that applies a specified accumulator function to the first item emitted by a source * ObservableSource and a seed value derived from calling a specified seedSupplier, then feeds the result * of that function along with the second item emitted by an ObservableSource into the same function, - * and so on until all items have been emitted by the source ObservableSource, emitting the final result + * and so on until all items have been emitted by the finite source ObservableSource, emitting the final result * from the final call to your function as its sole item. * <p> - * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceSeed.2.png" alt=""> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduceWith.o.png" alt=""> * <p> * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code reduceWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9080,7 +10410,7 @@ public final <R> Single<R> reduceWith(Callable<R> seedSupplier, BiFunction<R, ? /** * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource indefinitely. * <p> - * <img width="640" height="309" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeat.o.png" alt=""> + * <img width="640" height="287" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeatInf.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9099,7 +10429,7 @@ public final Observable<T> repeat() { * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource at most * {@code count} times. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeat.on.png" alt=""> + * <img width="640" height="336" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeatCount.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9130,15 +10460,16 @@ public final Observable<T> repeat(long times) { * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource until * the provided stop function returns true. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeat.on.png" alt=""> + * <img width="640" height="262" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeatUntil.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * * @param stop - * a boolean supplier that is called when the current Observable completes and unless it returns - * false, the current Observable is resubscribed + * a boolean supplier that is called when the current Observable completes; + * if it returns true, the returned Observable completes; if it returns false, + * the upstream Observable is resubscribed. * @return the new Observable instance * @throws NullPointerException * if {@code stop} is null @@ -9183,7 +10514,7 @@ public final Observable<T> repeatWhen(final Function<? super Observable<Object>, * ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items when it is * subscribed to, but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.png" alt=""> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9203,7 +10534,7 @@ public final ConnectableObservable<T> replay() { * Returns an Observable that emits items that are the results of invoking a specified selector on the items * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource. * <p> - * <img width="640" height="450" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.f.png" alt=""> + * <img width="640" height="449" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.f.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9230,7 +10561,10 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying {@code bufferSize} notifications. * <p> - * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fn.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="391" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9261,7 +10595,10 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying no more than {@code bufferSize} items that were emitted within a specified time window. * <p> - * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fnt.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fnt.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -9295,10 +10632,13 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying no more than {@code bufferSize} items that were emitted within a specified time window. * <p> - * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fnts.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="328" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fnts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -9338,10 +10678,13 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying a maximum of {@code bufferSize} items. * <p> - * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fns.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="362" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fns.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -9373,7 +10716,7 @@ public final <R> Observable<R> replay(final Function<? super Observable<T>, ? ex * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying all items that were emitted within a specified time window. * <p> - * <img width="640" height="435" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.ft.png" alt=""> + * <img width="640" height="393" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.ft.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -9404,10 +10747,10 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, * replaying all items that were emitted within a specified time window. * <p> - * <img width="640" height="440" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fts.png" alt=""> + * <img width="640" height="366" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -9439,10 +10782,10 @@ public final <R> Observable<R> replay(Function<? super Observable<T>, ? extends * Returns an Observable that emits items that are the results of invoking a specified selector on items * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource. * <p> - * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.fs.png" alt=""> + * <img width="640" height="406" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.fs.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param <R> @@ -9472,7 +10815,10 @@ public final <R> Observable<R> replay(final Function<? super Observable<T>, ? ex * an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, but only * when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.n.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.n.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9497,7 +10843,10 @@ public final ConnectableObservable<T> replay(final int bufferSize) { * ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items when it is * subscribed to, but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.nt.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.nt.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -9526,10 +10875,13 @@ public final ConnectableObservable<T> replay(int bufferSize, long time, TimeUnit * Connectable ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items * when it is subscribed to, but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.nts.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.nts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param bufferSize @@ -9562,10 +10914,13 @@ public final ConnectableObservable<T> replay(final int bufferSize, final long ti * an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, but only * when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.ns.png" alt=""> + * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + * <p> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.ns.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param bufferSize @@ -9589,7 +10944,7 @@ public final ConnectableObservable<T> replay(final int bufferSize, final Schedul * resembles an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, * but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.t.png" alt=""> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.t.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -9615,10 +10970,10 @@ public final ConnectableObservable<T> replay(long time, TimeUnit unit) { * resembles an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, * but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.ts.png" alt=""> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -9645,10 +11000,10 @@ public final ConnectableObservable<T> replay(final long time, final TimeUnit uni * {@link Scheduler}. A Connectable ObservableSource resembles an ordinary ObservableSource, except that it does not * begin emitting items when it is subscribed to, but only when its {@code connect} method is called. * <p> - * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.s.png" alt=""> + * <img width="640" height="445" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.o.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -9696,7 +11051,7 @@ public final Observable<T> retry() { * Returns an Observable that mirrors the source ObservableSource, resubscribing to it if it calls {@code onError} * and the predicate returns true for that specific exception and retry count. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png" alt=""> + * <img width="640" height="235" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.o.ne.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9721,7 +11076,7 @@ public final Observable<T> retry(BiPredicate<? super Integer, ? super Throwable> * Returns an Observable that mirrors the source ObservableSource, resubscribing to it if it calls {@code onError} * up to a specified number of retries. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png" alt=""> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.o.n.png" alt=""> * <p> * If the source ObservableSource calls {@link Observer#onError}, this method will resubscribe to the source * ObservableSource for a maximum of {@code count} resubscriptions rather than propagating the @@ -9737,7 +11092,7 @@ public final Observable<T> retry(BiPredicate<? super Integer, ? super Throwable> * </dl> * * @param times - * number of retry attempts before failing + * the number of times to resubscribe if the current Observable fails * @return the source ObservableSource modified with retry logic * @see <a href="http://reactivex.io/documentation/operators/retry.html">ReactiveX operators documentation: Retry</a> */ @@ -9750,12 +11105,12 @@ public final Observable<T> retry(long times) { /** * Retries at most times or until the predicate returns false, whichever happens first. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png" alt=""> + * <img width="640" height="269" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.o.nfe.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Observable fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Observable instance */ @@ -9773,7 +11128,7 @@ public final Observable<T> retry(long times, Predicate<? super Throwable> predic /** * Retries the current Observable if the predicate returns true. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png" alt=""> + * <img width="640" height="248" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.o.e.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9791,7 +11146,8 @@ public final Observable<T> retry(Predicate<? super Throwable> predicate) { /** * Retries until the given stop function returns true. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png" alt=""> * <dl> + * <img width="640" height="261" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retryUntil.o.png" alt=""> + * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryUntil} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> @@ -9814,19 +11170,19 @@ public final Observable<T> retryUntil(final BooleanSupplier stop) { * resubscribe to the source ObservableSource. * <p> * <img width="640" height="430" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retryWhen.f.png" alt=""> - * + * <p> * Example: * * This retries 3 times, each time incrementing the number of seconds it waits. * * <pre><code> - * ObservableSource.create((Observer<? super String> s) -> { + * Observable.create((ObservableEmitter<? super String> s) -> { * System.out.println("subscribing"); * s.onError(new RuntimeException("always fails")); * }).retryWhen(attempts -> { * return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> { * System.out.println("delay retry by " + i + " second(s)"); - * return ObservableSource.timer(i, TimeUnit.SECONDS); + * return Observable.timer(i, TimeUnit.SECONDS); * }); * }).blockingForEach(System.out::println); * </code></pre> @@ -9842,6 +11198,31 @@ public final Observable<T> retryUntil(final BooleanSupplier stop) { * delay retry by 3 second(s) * subscribing * } </pre> + * <p> + * Note that the inner {@code ObservableSource} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code ObservableSource} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + * <p> + * The following example demonstrates how to retry an asynchronous source with a delay: + * <pre><code> + * Observable.timer(1, TimeUnit.SECONDS) + * .doOnSubscribe(s -> System.out.println("subscribing")) + * .map(v -> { throw new RuntimeException(); }) + * .retryWhen(errors -> { + * AtomicInteger counter = new AtomicInteger(); + * return errors + * .takeWhile(e -> counter.getAndIncrement() != 3) + * .flatMap(e -> { + * System.out.println("delay retry by " + counter.get() + " second(s)"); + * return Observable.timer(counter.get(), TimeUnit.SECONDS); + * }); + * }) + * .blockingSubscribe(System.out::println, System.out::println); + * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -9865,21 +11246,21 @@ public final Observable<T> retryWhen( * Subscribes to the current Observable and wraps the given Observer into a SafeObserver * (if not already a SafeObserver) that * deals with exceptions thrown by a misbehaving Observer (that doesn't follow the - * Reactive-Streams specification). + * Reactive Streams specification). * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code safeSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * @param s the incoming Observer instance + * @param observer the incoming Observer instance * @throws NullPointerException if s is null */ @SchedulerSupport(SchedulerSupport.NONE) - public final void safeSubscribe(Observer<? super T> s) { - ObjectHelper.requireNonNull(s, "s is null"); - if (s instanceof SafeObserver) { - subscribe(s); + public final void safeSubscribe(Observer<? super T> observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + if (observer instanceof SafeObserver) { + subscribe(observer); } else { - subscribe(new SafeObserver<T>(s)); + subscribe(new SafeObserver<T>(observer)); } } @@ -9946,7 +11327,7 @@ public final Observable<T> sample(long period, TimeUnit unit, boolean emitLast) * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sample.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param period @@ -9976,7 +11357,7 @@ public final Observable<T> sample(long period, TimeUnit unit, Scheduler schedule * <img width="640" height="276" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sample.s.emitlast.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * <p>History: 2.0.5 - experimental @@ -10134,7 +11515,7 @@ public final Observable<T> scan(BiFunction<T, T, T> accumulator) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final <R> Observable<R> scan(final R initialValue, BiFunction<R, ? super T, R> accumulator) { - ObjectHelper.requireNonNull(initialValue, "seed is null"); + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); return scanWith(Functions.justCallable(initialValue), accumulator); } @@ -10201,13 +11582,13 @@ public final Observable<T> serialize() { } /** - * Returns a new {@link ObservableSource} that multicasts (shares) the original {@link ObservableSource}. As long as + * Returns a new {@link ObservableSource} that multicasts (and shares a single subscription to) the original {@link ObservableSource}. As long as * there is at least one {@link Observer} this {@link ObservableSource} will be subscribed and emitting data. * When all subscribers have disposed it will dispose the source {@link ObservableSource}. * <p> - * This is an alias for {@link #publish()}.{@link ConnectableObservable#refCount()}. + * This is an alias for {@link #publish()}.{@link ConnectableObservable#refCount() refCount()}. * <p> - * <img width="640" height="510" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishRefCount.png" alt=""> + * <img width="640" height="510" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishRefCount.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code share} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10224,11 +11605,10 @@ public final Observable<T> share() { } /** - * Returns a Maybe that emits the single item emitted by this Observable if this Observable - * emits only a single item, otherwise if this Observable emits more than one item or no items, an - * {@code IllegalArgumentException} or {@code NoSuchElementException} is signalled respectively. + * Returns a Maybe that completes if this Observable is empty or emits the single item emitted by this Observable, + * or signals an {@code IllegalArgumentException} if this Observable emits more than one item. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/singleElement.png" alt=""> + * <img width="640" height="217" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/singleElement.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code singleElement} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10522,7 +11902,7 @@ public final Observable<T> skipLast(long time, TimeUnit unit, Scheduler schedule * Note: this action will cache the latest items arriving in the specified time window. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -10603,10 +11983,12 @@ public final Observable<T> skipWhile(Predicate<? super T> predicate) { * Returns an Observable that emits the events emitted by source ObservableSource, in a * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all * other items in the sequence. - * - * <p>If any item emitted by this Observable does not implement {@link Comparable} with respect to - * all other items emitted by this Observable, no items will be emitted and the - * sequence is terminated with a {@link ClassCastException}. + * <p> + * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sorted.png" alt=""> + * <p> + * If any item emitted by this Observable does not implement {@link Comparable} with respect to + * all other items emitted by this Observable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. * * <p>Note that calling {@code sorted} with long, non-terminating or infinite sources * might cause {@link OutOfMemoryError} @@ -10698,7 +12080,7 @@ public final Observable<T> startWith(ObservableSource<? extends T> other) { * Returns an Observable that emits a specified item before it begins to emit items emitted by the source * ObservableSource. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/startWith.png" alt=""> + * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/startWith.item.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code startWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10722,7 +12104,7 @@ public final Observable<T> startWith(T item) { * Returns an Observable that emits the specified items before it begins to emit items emitted by the source * ObservableSource. * <p> - * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/startWith.png" alt=""> + * <img width="640" height="315" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/startWithArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code startWithArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -10870,7 +12252,8 @@ public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super T * @throws NullPointerException * if {@code onNext} is null, or * if {@code onError} is null, or - * if {@code onComplete} is null + * if {@code onComplete} is null, or + * if {@code onSubscribe} is null * @see <a href="http://reactivex.io/documentation/operators/subscribe.html">ReactiveX operators documentation: Subscribe</a> */ @CheckReturnValue @@ -10896,7 +12279,7 @@ public final void subscribe(Observer<? super T> observer) { try { observer = RxJavaPlugins.onSubscribe(this, observer); - ObjectHelper.requireNonNull(observer, "Plugin returned null Observer"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); subscribeActual(observer); } catch (NullPointerException e) { // NOPMD @@ -10915,9 +12298,10 @@ public final void subscribe(Observer<? super T> observer) { /** * Operator implementations (both source and intermediate) should implement this method that - * performs the necessary business logic. - * <p>There is no need to call any of the plugin hooks on the current Observable instance or - * the Subscriber. + * performs the necessary business logic and handles the incoming {@link Observer}s. + * <p>There is no need to call any of the plugin hooks on the current {@code Observable} instance or + * the {@code Observer}; all hooks and basic safeguards have been + * applied by {@link #subscribe(Observer)} before this method gets called. * @param observer the incoming Observer, never null */ protected abstract void subscribeActual(Observer<? super T> observer); @@ -10930,11 +12314,11 @@ public final void subscribe(Observer<? super T> observer) { * Observable<Integer> source = Observable.range(1, 10); * CompositeDisposable composite = new CompositeDisposable(); * - * ResourceObserver<Integer> rs = new ResourceObserver<>() { + * DisposableObserver<Integer> ds = new DisposableObserver<>() { * // ... * }; * - * composite.add(source.subscribeWith(rs)); + * composite.add(source.subscribeWith(ds)); * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> @@ -10959,7 +12343,7 @@ public final <E extends Observer<? super T>> E subscribeWith(E observer) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/subscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -10980,8 +12364,8 @@ public final Observable<T> subscribeOn(Scheduler scheduler) { /** * Returns an Observable that emits the items emitted by the source ObservableSource or the items of an alternate * ObservableSource if the source ObservableSource is empty. + * <p> * <img width="640" height="255" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchifempty.png" alt=""> - * <p/> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.</dd> @@ -11020,6 +12404,7 @@ public final Observable<T> switchIfEmpty(ObservableSource<? extends T> other) { * ObservableSource * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMapDelayError(Function) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -11049,6 +12434,7 @@ public final <R> Observable<R> switchMap(Function<? super T, ? extends Observabl * the number of elements to prefetch from the current active inner ObservableSource * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -11066,6 +12452,151 @@ public final <R> Observable<R> switchMap(Function<? super T, ? extends Observabl return RxJavaPlugins.onAssembly(new ObservableSwitchMap<T, R>(this, mapper, bufferSize, false)); } + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + * <p> + * <img width="640" height="521" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapCompletable.f.png" alt=""> + * <p> + * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Observable}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapCompletable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If either this {@code Observable} or the active {@code CompletableSource} signals an {@code onError}, + * the resulting {@code Completable} is terminated immediately with that {@code Throwable}. + * Use the {@link #switchMapCompletableDelayError(Function)} to delay such inner failures until + * every inner {@code CompletableSource}s and the main {@code Observable} terminates in some fashion. + * If they fail concurrently, the operator may combine the {@code Throwable}s into a + * {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signal it to the downstream instead. If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signalled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + * </dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletable(@NonNull Function<? super T, ? extends CompletableSource> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapCompletable<T>(this, mapper, false)); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running and delaying any main or inner errors until all + * of them terminate. + * <p> + * <img width="640" height="453" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapCompletableDelayError.f.png" alt=""> + * <p> + * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Observable}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>Errors of this {@code Observable} and all the {@code CompletableSource}s, who had the chance + * to run to their completion, are delayed until + * all of them terminate in some fashion. At this point, if there was only one failure, the respective + * {@code Throwable} is emitted to the downstream. It there were more than one failures, the + * operator combines all {@code Throwable}s into a {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signals that to the downstream. + * If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signalled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + * </dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletableDelayError(@NonNull Function<? super T, ? extends CompletableSource> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapCompletable<T>(this, mapper, true)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available while failing immediately if this {@code Observable} or any of the + * active inner {@code MaybeSource}s fail. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.o.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapMaybe} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>This operator terminates with an {@code onError} if this {@code Observable} or any of + * the inner {@code MaybeSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Observable} or from any of + * the inner {@code MaybeSource}s will be forwarded to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Observable instance + * @see #switchMapMaybeDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> switchMapMaybe(@NonNull Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapMaybe<T, R>(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available, delaying errors from this {@code Observable} or the inner {@code MaybeSource}s until all terminate. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code switchMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.11 - experimental + * @param <R> the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Observable instance + * @see #switchMapMaybe(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> Observable<R> switchMapMaybeDelayError(@NonNull Function<? super T, ? extends MaybeSource<? extends R>> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapMaybe<T, R>(this, mapper, true)); + } + /** * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source * ObservableSource that returns a SingleSource, and then emitting the item emitted by the most recently emitted @@ -11074,26 +12605,27 @@ public final <R> Observable<R> switchMap(Function<? super T, ? extends Observabl * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner SingleSource, if any, complete. * If the upstream ObservableSource signals an onError, the inner SingleSource is disposed and the error delivered in-sequence. * <p> - * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <img width="640" height="531" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapSingle.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code switchMapSingle} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * + * <p>History: 2.0.8 - experimental * @param <R> the element type of the inner SingleSources and the output * @param mapper * a function that, when applied to an item emitted by the source ObservableSource, returns a * SingleSource * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> - * @since 2.0.8 + * @see #switchMapSingleDelayError(Function) + * @since 2.2 */ - @Experimental @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @NonNull public final <R> Observable<R> switchMapSingle(@NonNull Function<? super T, ? extends SingleSource<? extends R>> mapper) { - return ObservableInternalHelper.switchMapSingle(this, mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapSingle<T, R>(this, mapper, false)); } /** @@ -11105,26 +12637,27 @@ public final <R> Observable<R> switchMapSingle(@NonNull Function<? super T, ? ex * If the upstream ObservableSource signals an onError, the termination of the last inner SingleSource will emit that error as is * or wrapped into a CompositeException along with the other possible errors the former inner SingleSources signalled. * <p> - * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png" alt=""> + * <img width="640" height="467" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMapSingleDelayError.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code switchMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> - * + * <p>History: 2.0.8 - experimental * @param <R> the element type of the inner SingleSources and the output * @param mapper * a function that, when applied to an item emitted by the source ObservableSource, returns a * SingleSource * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> - * @since 2.0.8 + * @see #switchMapSingle(Function) + * @since 2.2 */ - @Experimental @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @NonNull public final <R> Observable<R> switchMapSingleDelayError(@NonNull Function<? super T, ? extends SingleSource<? extends R>> mapper) { - return ObservableInternalHelper.switchMapSingleDelayError(this, mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapSingle<T, R>(this, mapper, true)); } /** @@ -11148,6 +12681,7 @@ public final <R> Observable<R> switchMapSingleDelayError(@NonNull Function<? sup * ObservableSource * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -11179,6 +12713,7 @@ public final <R> Observable<R> switchMapDelayError(Function<? super T, ? extends * the number of elements to prefetch from the current active inner ObservableSource * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -11230,6 +12765,9 @@ public final Observable<T> take(long count) { * Returns an Observable that emits those items emitted by source ObservableSource before a specified time runs * out. * <p> + * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/take.t.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> @@ -11253,10 +12791,13 @@ public final Observable<T> take(long time, TimeUnit unit) { * Returns an Observable that emits those items emitted by source ObservableSource before a specified time (on a * specified Scheduler) runs out. * <p> + * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/take.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -11298,10 +12839,10 @@ public final Observable<T> take(long time, TimeUnit unit, Scheduler scheduler) { public final Observable<T> takeLast(int count) { if (count < 0) { throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); - } else + } if (count == 0) { return RxJavaPlugins.onAssembly(new ObservableIgnoreElements<T>(this)); - } else + } if (count == 1) { return RxJavaPlugins.onAssembly(new ObservableTakeLastOne<T>(this)); } @@ -11469,7 +13010,7 @@ public final Observable<T> takeLast(long time, TimeUnit unit, boolean delayError * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeLast.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -11497,7 +13038,7 @@ public final Observable<T> takeLast(long time, TimeUnit unit, Scheduler schedule * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeLast.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -11528,7 +13069,7 @@ public final Observable<T> takeLast(long time, TimeUnit unit, Scheduler schedule * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeLast.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param time @@ -11603,7 +13144,7 @@ public final <U> Observable<T> takeUntil(ObservableSource<U> other) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable<T> takeUntil(Predicate<? super T> stopPredicate) { - ObjectHelper.requireNonNull(stopPredicate, "predicate is null"); + ObjectHelper.requireNonNull(stopPredicate, "stopPredicate is null"); return RxJavaPlugins.onAssembly(new ObservableTakeUntilPredicate<T>(this, stopPredicate)); } @@ -11667,7 +13208,7 @@ public final Observable<T> throttleFirst(long windowDuration, TimeUnit unit) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param skipDuration @@ -11726,7 +13267,7 @@ public final Observable<T> throttleLast(long intervalDuration, TimeUnit unit) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLast.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param intervalDuration @@ -11748,21 +13289,142 @@ public final Observable<T> throttleLast(long intervalDuration, TimeUnit unit, Sc } /** - * Returns an Observable that only emits those items emitted by the source ObservableSource that are not followed - * by another emitted item within a specified time window. + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. * <p> - * <em>Note:</em> If the source ObservableSource keeps emitting items more frequently than the length of the time - * window then no items will be emitted by the resulting ObservableSource. + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.png" alt=""> * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.png" alt=""> + * Unlike the option with {@link #throttleLatest(long, TimeUnit, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. * <p> - * Information on debounce vs throttle: + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, boolean) + * @see #throttleLatest(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable<T> throttleLatest(long timeout, TimeUnit unit) { + return throttleLatest(timeout, unit, Schedulers.computation(), false); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.e.png" alt=""> + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable<T> throttleLatest(long timeout, TimeUnit unit, boolean emitLast) { + return throttleLatest(timeout, unit, Schedulers.computation(), emitLast); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.s.png" alt=""> + * <p> + * Unlike the option with {@link #throttleLatest(long, TimeUnit, Scheduler, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable<T> throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler) { + return throttleLatest(timeout, unit, scheduler, false); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLatest.se.png" alt=""> + * <p> + * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable<T> throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableThrottleLatest<T>(this, timeout, unit, scheduler, emitLast)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires. The timer resets on + * each emission (alias to {@link #debounce(long, TimeUnit, Scheduler)}). + * <p> + * <em>Note:</em> If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code throttleWithTimeout} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -11773,8 +13435,9 @@ public final Observable<T> throttleLast(long intervalDuration, TimeUnit unit, Sc * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the * resulting ObservableSource * @param unit - * the {@link TimeUnit} of {@code timeout} - * @return an Observable that filters out items that are too quickly followed by newer items + * the unit of time for the specified {@code timeout} + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> * @see #debounce(long, TimeUnit) */ @@ -11785,25 +13448,17 @@ public final Observable<T> throttleWithTimeout(long timeout, TimeUnit unit) { } /** - * Returns an Observable that only emits those items emitted by the source ObservableSource that are not followed - * by another emitted item within a specified time window, where the time window is governed by a specified - * Scheduler. + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission (Alias to {@link #debounce(long, TimeUnit, Scheduler)}). * <p> - * <em>Note:</em> If the source ObservableSource keeps emitting items more frequently than the length of the time - * window then no items will be emitted by the resulting ObservableSource. + * <em>Note:</em> If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleWithTimeout.s.png" alt=""> - * <p> - * Information on debounce vs throttle: - * <p> - * <ul> - * <li><a href="http://drupalmotion.com/article/debounce-and-throttle-visual-explanation">Debounce and Throttle: visual explanation</a></li> - * <li><a href="http://unscriptable.com/2009/03/20/debouncing-javascript-methods/">Debouncing: javascript methods</a></li> - * <li><a href="http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/">Javascript - don't spam your server: debounce and throttle</a></li> - * </ul> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -11811,11 +13466,12 @@ public final Observable<T> throttleWithTimeout(long timeout, TimeUnit unit) { * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the * resulting ObservableSource * @param unit - * the {@link TimeUnit} of {@code timeout} + * the unit of time for the specified {@code timeout} * @param scheduler * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each * item - * @return an Observable that filters out items that are too quickly followed by newer items + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items * @see <a href="http://reactivex.io/documentation/operators/debounce.html">ReactiveX operators documentation: Debounce</a> * @see #debounce(long, TimeUnit, Scheduler) */ @@ -11883,7 +13539,7 @@ public final Observable<Timed<T>> timeInterval(Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/timeinterval.html">ReactiveX operators documentation: TimeInterval</a> */ @CheckReturnValue - @SchedulerSupport(SchedulerSupport.NONE) // Trampoline scheduler is only used for creating timestamps. + @SchedulerSupport(SchedulerSupport.NONE) public final Observable<Timed<T>> timeInterval(TimeUnit unit) { return timeInterval(unit, Schedulers.computation()); } @@ -12005,7 +13661,8 @@ public final Observable<T> timeout(long timeout, TimeUnit timeUnit) { /** * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, - * the resulting ObservableSource begins instead to mirror a fallback ObservableSource. + * the source ObservableSource is disposed and resulting ObservableSource begins instead + * to mirror a fallback ObservableSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2.png" alt=""> * <dl> @@ -12032,12 +13689,13 @@ public final Observable<T> timeout(long timeout, TimeUnit timeUnit, ObservableSo /** * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration - * starting from its predecessor, the resulting ObservableSource begins instead to mirror a fallback ObservableSource. + * starting from its predecessor, the source ObservableSource is disposed and resulting ObservableSource + * begins instead to mirror a fallback ObservableSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -12068,7 +13726,7 @@ public final Observable<T> timeout(long timeout, TimeUnit timeUnit, Scheduler sc * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.1s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timeout @@ -12360,7 +14018,7 @@ public final Single<List<T>> toList(final int capacityHint) { * Returns a Single that emits a single item, a list composed of all the items emitted by the * finite source ObservableSource. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toList.2.png" alt=""> + * <img width="640" height="365" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toList.o.c.png" alt=""> * <p> * Normally, an ObservableSource that returns multiple items will do so by invoking its {@link Observer}'s * {@link Observer#onNext onNext} method for each such item. You can change this behavior, instructing the @@ -12391,12 +14049,17 @@ public final <U extends Collection<? super T>> Single<U> toList(Callable<U> coll } /** - * Returns a Single that emits a single HashMap containing all items emitted by the source ObservableSource, - * mapped by the keys returned by a specified {@code keySelector} function. + * Returns a Single that emits a single HashMap containing all items emitted by the + * finite source ObservableSource, mapped by the keys returned by a specified + * {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.2.png" alt=""> * <p> * If more than one source item maps to the same key, the HashMap will contain the latest of those items. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12418,12 +14081,16 @@ public final <K> Single<Map<K, T>> toMap(final Function<? super T, ? extends K> /** * Returns a Single that emits a single HashMap containing values corresponding to items emitted by the - * source ObservableSource, mapped by the keys returned by a specified {@code keySelector} function. + * finite source ObservableSource, mapped by the keys returned by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.2.png" alt=""> * <p> * If more than one source item maps to the same key, the HashMap will contain a single entry that * corresponds to the latest of those items. + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12451,9 +14118,13 @@ public final <K, V> Single<Map<K, V>> toMap( /** * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that - * contains keys and values extracted from the items emitted by the source ObservableSource. + * contains keys and values extracted from the items emitted by the finite source ObservableSource. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMap.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12478,7 +14149,6 @@ public final <K, V> Single<Map<K, V>> toMap( final Function<? super T, ? extends V> valueSelector, Callable<? extends Map<K, V>> mapSupplier) { ObjectHelper.requireNonNull(keySelector, "keySelector is null"); - ObjectHelper.requireNonNull(keySelector, "keySelector is null"); ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); ObjectHelper.requireNonNull(mapSupplier, "mapSupplier is null"); return collect(mapSupplier, Functions.toMapKeyValueSelector(keySelector, valueSelector)); @@ -12486,9 +14156,13 @@ public final <K, V> Single<Map<K, V>> toMap( /** * Returns a Single that emits a single HashMap that contains an ArrayList of items emitted by the - * source ObservableSource keyed by a specified {@code keySelector} function. + * finite source ObservableSource keyed by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMultimap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12513,10 +14187,14 @@ public final <K> Single<Map<K, Collection<T>>> toMultimap(Function<? super T, ? /** * Returns a Single that emits a single HashMap that contains an ArrayList of values extracted by a - * specified {@code valueSelector} function from items emitted by the source ObservableSource, keyed by a - * specified {@code keySelector} function. + * specified {@code valueSelector} function from items emitted by the finite source ObservableSource, + * keyed by a specified {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMultimap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12582,9 +14260,13 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap( /** * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that * contains an ArrayList of values, extracted by a specified {@code valueSelector} function from items - * emitted by the source ObservableSource and keyed by the {@code keySelector} function. + * emitted by the finite source ObservableSource and keyed by the {@code keySelector} function. * <p> * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toMultiMap.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMultimap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12614,6 +14296,30 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap( /** * Converts the current Observable into a Flowable by applying the specified backpressure strategy. + * <p> + * Marble diagrams for the various backpressure strategies are as follows: + * <ul> + * <li>{@link BackpressureStrategy#BUFFER} + * <p> + * <img width="640" height="274" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toFlowable.o.buffer.png" alt=""> + * </li> + * <li>{@link BackpressureStrategy#DROP} + * <p> + * <img width="640" height="389" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toFlowable.o.drop.png" alt=""> + * </li> + * <li>{@link BackpressureStrategy#LATEST} + * <p> + * <img width="640" height="296" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toFlowable.o.latest.png" alt=""> + * </li> + * <li>{@link BackpressureStrategy#ERROR} + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toFlowable.o.error.png" alt=""> + * </li> + * <li>{@link BackpressureStrategy#MISSING} + * <p> + * <img width="640" height="411" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toFlowable.o.missing.png" alt=""> + * </li> + * </ul> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The operator applies the chosen backpressure strategy of {@link BackpressureStrategy} enum.</dd> @@ -12628,24 +14334,24 @@ public final <K, V> Single<Map<K, Collection<V>>> toMultimap( @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Flowable<T> toFlowable(BackpressureStrategy strategy) { - Flowable<T> o = new FlowableFromObservable<T>(this); + Flowable<T> f = new FlowableFromObservable<T>(this); switch (strategy) { case DROP: - return o.onBackpressureDrop(); + return f.onBackpressureDrop(); case LATEST: - return o.onBackpressureLatest(); + return f.onBackpressureLatest(); case MISSING: - return o; + return f; case ERROR: - return RxJavaPlugins.onAssembly(new FlowableOnBackpressureError<T>(o)); + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureError<T>(f)); default: - return o.onBackpressureBuffer(); + return f.onBackpressureBuffer(); } } /** - * Returns a Single that emits a list that contains the items emitted by the source ObservableSource, in a + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all * other items in the sequence. * @@ -12654,6 +14360,10 @@ public final Flowable<T> toFlowable(BackpressureStrategy strategy) { * sequence is terminated with a {@link ClassCastException}. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSortedList} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12669,10 +14379,14 @@ public final Single<List<T>> toSortedList() { } /** - * Returns a Single that emits a list that contains the items emitted by the source ObservableSource, in a + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a * sorted order based on a specified comparison function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.f.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSortedList} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12693,10 +14407,14 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator } /** - * Returns a Single that emits a list that contains the items emitted by the source ObservableSource, in a + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a * sorted order based on a specified comparison function. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.f.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSortedList} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12720,7 +14438,7 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator } /** - * Returns a Single that emits a list that contains the items emitted by the source ObservableSource, in a + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all * other items in the sequence. * @@ -12729,6 +14447,10 @@ public final Single<List<T>> toSortedList(final Comparator<? super T> comparator * sequence is terminated with a {@link ClassCastException}. * <p> * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/toSortedList.2.png" alt=""> + * <p> + * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toSortedList} does not operate by default on a particular {@link Scheduler}.</dd> @@ -12750,9 +14472,11 @@ public final Single<List<T>> toSortedList(int capacityHint) { /** * Modifies the source ObservableSource so that subscribers will dispose it on a specified * {@link Scheduler}. + * <p> + * <img width="640" height="452" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/unsubscribeOn.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -12892,7 +14616,7 @@ public final Observable<Observable<T>> window(long timespan, long timeskip, Time * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window7.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -12922,7 +14646,7 @@ public final Observable<Observable<T>> window(long timespan, long timeskip, Time * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window7.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -13051,7 +14775,7 @@ public final Observable<Observable<T>> window(long timespan, TimeUnit unit, * <img width="640" height="375" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window5.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -13082,7 +14806,7 @@ public final Observable<Observable<T>> window(long timespan, TimeUnit unit, * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -13116,7 +14840,7 @@ public final Observable<Observable<T>> window(long timespan, TimeUnit unit, * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -13152,7 +14876,7 @@ public final Observable<Observable<T>> window(long timespan, TimeUnit unit, * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param timespan @@ -13612,6 +15336,8 @@ public final <U, R> Observable<R> zipWith(Iterable<U> other, BiFunction<? super * Returns an Observable that emits items that are the result of applying a specified function to pairs of * values, one each from the source ObservableSource and another specified ObservableSource. * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> + * <p> * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while disposing the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -13623,8 +15349,6 @@ public final <U, R> Observable<R> zipWith(Iterable<U> other, BiFunction<? super * <br>To work around this termination property, * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion * or a dispose() call. - * - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code zipWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -13655,6 +15379,8 @@ public final <U, R> Observable<R> zipWith(ObservableSource<? extends U> other, * Returns an Observable that emits items that are the result of applying a specified function to pairs of * values, one each from the source ObservableSource and another specified ObservableSource. * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> + * <p> * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while disposing the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -13666,8 +15392,6 @@ public final <U, R> Observable<R> zipWith(ObservableSource<? extends U> other, * <br>To work around this termination property, * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion * or a dispose() call. - * - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code zipWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -13700,6 +15424,8 @@ public final <U, R> Observable<R> zipWith(ObservableSource<? extends U> other, * Returns an Observable that emits items that are the result of applying a specified function to pairs of * values, one each from the source ObservableSource and another specified ObservableSource. * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> + * <p> * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while disposing the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -13711,8 +15437,6 @@ public final <U, R> Observable<R> zipWith(ObservableSource<? extends U> other, * <br>To work around this termination property, * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion * or a dispose() call. - * - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code zipWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -13759,9 +15483,9 @@ public final <U, R> Observable<R> zipWith(ObservableSource<? extends U> other, @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test() { // NoPMD - TestObserver<T> ts = new TestObserver<T>(); - subscribe(ts); - return ts; + TestObserver<T> to = new TestObserver<T>(); + subscribe(to); + return to; } /** @@ -13779,11 +15503,11 @@ public final TestObserver<T> test() { // NoPMD @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test(boolean dispose) { // NoPMD - TestObserver<T> ts = new TestObserver<T>(); + TestObserver<T> to = new TestObserver<T>(); if (dispose) { - ts.dispose(); + to.dispose(); } - subscribe(ts); - return ts; + subscribe(to); + return to; } } diff --git a/src/main/java/io/reactivex/ObservableConverter.java b/src/main/java/io/reactivex/ObservableConverter.java new file mode 100644 index 0000000000..12c461523d --- /dev/null +++ b/src/main/java/io/reactivex/ObservableConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Observable#as} operator to turn an Observable into another + * value fluently. + * <p>History: 2.1.7 - experimental + * @param <T> the upstream type + * @param <R> the output type + * @since 2.2 + */ +public interface ObservableConverter<T, R> { + /** + * Applies a function to the upstream Observable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Observable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Observable<T> upstream); +} diff --git a/src/main/java/io/reactivex/ObservableEmitter.java b/src/main/java/io/reactivex/ObservableEmitter.java index bd2aac8eb1..9faccf528d 100644 --- a/src/main/java/io/reactivex/ObservableEmitter.java +++ b/src/main/java/io/reactivex/ObservableEmitter.java @@ -21,32 +21,54 @@ * Abstraction over an RxJava {@link Observer} that allows associating * a resource with it. * <p> - * The onNext, onError and onComplete methods should be called - * in a sequential manner, just like the Observer's methods. - * Use {@link #serialize()} if you want to ensure this. + * The {@link #onNext(Object)}, {@link #onError(Throwable)}, {@link #tryOnError(Throwable)} + * and {@link #onComplete()} methods should be called in a sequential manner, just like the + * {@link Observer}'s methods should be. + * Use the {@code ObservableEmitter} the {@link #serialize()} method returns instead of the original + * {@code ObservableEmitter} instance provided by the generator routine if you want to ensure this. * The other methods are thread-safe. + * <p> + * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onError(Throwable)}, + * {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + * <p> + * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + * <p> + * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. * * @param <T> the value type to emit */ public interface ObservableEmitter<T> extends Emitter<T> { /** - * Sets a Disposable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param d the disposable, null is allowed */ void setDisposable(@Nullable Disposable d); /** - * Sets a Cancellable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param c the cancellable resource, null is allowed */ void setCancellable(@Nullable Cancellable c); /** - * Returns true if the downstream disposed the sequence. - * @return true if the downstream disposed the sequence + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. + * <p>This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated */ boolean isDisposed(); @@ -64,11 +86,11 @@ public interface ObservableEmitter<T> extends Emitter<T> { * <p> * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called * if the error could not be delivered. + * <p>History: 2.1.1 - experimental * @param t the throwable error to signal if possible * @return true if successful, false if the downstream is not able to accept further * events - * @since 2.1.1 - experimental + * @since 2.2 */ - @Experimental boolean tryOnError(@NonNull Throwable t); } diff --git a/src/main/java/io/reactivex/ObservableOnSubscribe.java b/src/main/java/io/reactivex/ObservableOnSubscribe.java index 6ed97c7779..bce34e1925 100644 --- a/src/main/java/io/reactivex/ObservableOnSubscribe.java +++ b/src/main/java/io/reactivex/ObservableOnSubscribe.java @@ -25,9 +25,9 @@ public interface ObservableOnSubscribe<T> { /** * Called for each Observer that subscribes. - * @param e the safe emitter instance, never null + * @param emitter the safe emitter instance, never null * @throws Exception on error */ - void subscribe(@NonNull ObservableEmitter<T> e) throws Exception; + void subscribe(@NonNull ObservableEmitter<T> emitter) throws Exception; } diff --git a/src/main/java/io/reactivex/Observer.java b/src/main/java/io/reactivex/Observer.java index a383d04a27..4c446e67fb 100644 --- a/src/main/java/io/reactivex/Observer.java +++ b/src/main/java/io/reactivex/Observer.java @@ -19,14 +19,56 @@ /** * Provides a mechanism for receiving push-based notifications. * <p> - * After an Observer calls an {@link Observable}'s {@link Observable#subscribe subscribe} method, - * first the Observable calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows - * cancelling the sequence at any time, then the - * {@code Observable} may call the Observer's {@link #onNext} method any number of times + * When an {@code Observer} is subscribed to an {@link ObservableSource} through the {@link ObservableSource#subscribe(Observer)} method, + * the {@code ObservableSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time, then the + * {@code ObservableSource} may call the Observer's {@link #onNext} method any number of times * to provide notifications. A well-behaved - * {@code Observable} will call an Observer's {@link #onComplete} method exactly once or the Observer's + * {@code ObservableSource} will call an {@code Observer}'s {@link #onComplete} method exactly once or the {@code Observer}'s * {@link #onError} method exactly once. - * + * <p> + * Calling the {@code Observer}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + * <pre><code> onSubscribe onNext* (onError | onComplete)?</code></pre> + * <p> + * Subscribing an {@code Observer} to multiple {@code ObservableSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code Observer} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + * <p> + * Calling {@link #onSubscribe(Disposable)}, {@link #onNext(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + * <p> + * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases + * (see <a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a> of the Reactive Streams specification): + * <ul> + * <li>If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + * </li> + * <li>If there is a fatal error (such as {@code VirtualMachineError}).</li> + * </ul> + * <p> + * Violating Rule 2.13 results in undefined flow behavior. Generally, the following can happen: + * <ul> + * <li>An upstream operator turns it into an {@link #onError} call.</li> + * <li>If the flow is synchronous, the {@link ObservableSource#subscribe(Observer)} throws instead of returning normally.</li> + * <li>If the flow is asynchronous, the exception propagates up to the component ({@link Scheduler} or {@link java.util.concurrent.Executor}) + * providing the asynchronous boundary the code is running and either routes the exception to the global + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} handler or the current thread's + * {@link java.lang.Thread.UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} handler.</li> + * </ul> + * From the {@code Observable}'s perspective, an {@code Observer} is the end consumer thus it is the {@code Observer}'s + * responsibility to handle the error case and signal it "further down". This means unreliable code in the {@code onXXX} + * methods should be wrapped into `try-catch`es, specifically in {@link #onError(Throwable)} or {@link #onComplete()}, and handled there + * (for example, by logging it or presenting the user with an error dialog). However, if the error would be thrown from + * {@link #onNext(Object)}, <a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a> mandates + * the implementation calls {@link Disposable#dispose()} and signals the exception in a way that is adequate to the target context, + * for example, by calling {@link #onError(Throwable)} on the same {@code Observer} instance. + * <p> + * If, for some reason, the {@code Observer} won't follow Rule 2.13, the {@link Observable#safeSubscribe(Observer)} can wrap it + * with the necessary safeguards and route exceptions thrown from {@code onNext} into {@code onError} and route exceptions thrown + * from {@code onError} and {@code onComplete} into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. * @see <a href="http://reactivex.io/documentation/observable.html">ReactiveX documentation: Observable</a> * @param <T> * the type of item the Observer expects to observe diff --git a/src/main/java/io/reactivex/Scheduler.java b/src/main/java/io/reactivex/Scheduler.java index 00eb314006..2e99e288f1 100644 --- a/src/main/java/io/reactivex/Scheduler.java +++ b/src/main/java/io/reactivex/Scheduler.java @@ -13,8 +13,6 @@ package io.reactivex; -import java.util.concurrent.TimeUnit; - import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; @@ -23,17 +21,78 @@ import io.reactivex.internal.schedulers.*; import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.SchedulerRunnableIntrospection; + +import java.util.concurrent.TimeUnit; /** * A {@code Scheduler} is an object that specifies an API for scheduling - * units of work with or without delays or periodically. - * You can get common instances of this class in {@link io.reactivex.schedulers.Schedulers}. + * units of work provided in the form of {@link Runnable}s to be + * executed without delay (effectively as soon as possible), after a specified time delay or periodically + * and represents an abstraction over an asynchronous boundary that ensures + * these units of work get executed by some underlying task-execution scheme + * (such as custom Threads, event loop, {@link java.util.concurrent.Executor Executor} or Actor system) + * with some uniform properties and guarantees regardless of the particular underlying + * scheme. + * <p> + * You can get various standard, RxJava-specific instances of this class via + * the static methods of the {@link io.reactivex.schedulers.Schedulers} utility class. + * <p> + * The so-called {@link Worker}s of a {@code Scheduler} can be created via the {@link #createWorker()} method which allow the scheduling + * of multiple {@link Runnable} tasks in an isolated manner. {@code Runnable} tasks scheduled on a {@code Worker} are guaranteed to be + * executed sequentially and in a non-overlapping fashion. Non-delayed {@code Runnable} tasks are guaranteed to execute in a + * First-In-First-Out order but their execution may be interleaved with delayed tasks. + * In addition, outstanding or running tasks can be cancelled together via + * {@link Worker#dispose()} without affecting any other {@code Worker} instances of the same {@code Scheduler}. + * <p> + * Implementations of the {@link #scheduleDirect} and {@link Worker#schedule} methods are encouraged to call the {@link io.reactivex.plugins.RxJavaPlugins#onSchedule(Runnable)} + * method to allow a scheduler hook to manipulate (wrap or replace) the original {@code Runnable} task before it is submitted to the + * underlying task-execution scheme. + * <p> + * The default implementations of the {@code scheduleDirect} methods provided by this abstract class + * delegate to the respective {@code schedule} methods in the {@link Worker} instance created via {@link #createWorker()} + * for each individual {@link Runnable} task submitted. Implementors of this class are encouraged to provide + * a more efficient direct scheduling implementation to avoid the time and memory overhead of creating such {@code Worker}s + * for every task. + * This delegation is done via special wrapper instances around the original {@code Runnable} before calling the respective + * {@code Worker.schedule} method. Note that this can lead to multiple {@code RxJavaPlugins.onSchedule} calls and potentially + * multiple hooks applied. Therefore, the default implementations of {@code scheduleDirect} (and the {@link Worker#schedulePeriodically(Runnable, long, long, TimeUnit)}) + * wrap the incoming {@code Runnable} into a class that implements the {@link io.reactivex.schedulers.SchedulerRunnableIntrospection} + * interface which can grant access to the original or hooked {@code Runnable}, thus, a repeated {@code RxJavaPlugins.onSchedule} + * can detect the earlier hook and not apply a new one over again. + * <p> + * The default implementation of {@link #now(TimeUnit)} and {@link Worker#now(TimeUnit)} methods to return current + * {@link System#currentTimeMillis()} value in the desired time unit. Custom {@code Scheduler} implementations can override this + * to provide specialized time accounting (such as virtual time to be advanced programmatically). + * Note that operators requiring a {@code Scheduler} may rely on either of the {@code now()} calls provided by + * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically + * consistent source of the current time. + * <p> + * The default implementation of the {@link Worker#schedulePeriodically(Runnable, long, long, TimeUnit)} method uses + * the {@link Worker#schedule(Runnable, long, TimeUnit)} for scheduling the {@code Runnable} task periodically. + * The algorithm calculates the next absolute time when the task should run again and schedules this execution + * based on the relative time between it and {@link Worker#now(TimeUnit)}. However, drifts or changes in the + * system clock could affect this calculation either by scheduling subsequent runs too frequently or too far apart. + * Therefore, the default implementation uses the {@link #clockDriftTolerance()} value (set via + * {@code rx2.scheduler.drift-tolerance} in minutes) to detect a drift in {@link Worker#now(TimeUnit)} and + * re-adjust the absolute/relative time calculation accordingly. + * <p> + * The default implementations of {@link #start()} and {@link #shutdown()} do nothing and should be overridden if the + * underlying task-execution scheme supports stopping and restarting itself. + * <p> + * If the {@code Scheduler} is shut down or a {@code Worker} is disposed, the {@code schedule} methods + * should return the {@link io.reactivex.disposables.Disposables#disposed()} singleton instance indicating the shut down/disposed + * state to the caller. Since the shutdown or dispose can happen from any thread, the {@code schedule} implementations + * should make best effort to cancel tasks immediately after those tasks have been submitted to the + * underlying task-execution scheme if the shutdown/dispose was detected after this submission. + * <p> + * All methods on the {@code Scheduler} and {@code Worker} classes should be thread safe. */ public abstract class Scheduler { /** * The tolerance for a clock drift in nanoseconds where the periodic scheduler will rebase. * <p> - * The associated system parameter, {@code rx.scheduler.drift-tolerance}, expects its value in minutes. + * The associated system parameter, {@code rx2.scheduler.drift-tolerance}, expects its value in minutes. */ static final long CLOCK_DRIFT_TOLERANCE_NANOSECONDS; static { @@ -43,7 +102,7 @@ public abstract class Scheduler { /** * Returns the clock drift tolerance in nanoseconds. - * <p>Related system property: {@code rx2.scheduler.drift-tolerance} in minutes + * <p>Related system property: {@code rx2.scheduler.drift-tolerance} in minutes. * @return the tolerance in nanoseconds * @since 2.0 */ @@ -51,13 +110,14 @@ public static long clockDriftTolerance() { return CLOCK_DRIFT_TOLERANCE_NANOSECONDS; } - /** - * Retrieves or creates a new {@link Scheduler.Worker} that represents serial execution of actions. + * Retrieves or creates a new {@link Scheduler.Worker} that represents sequential execution of actions. * <p> - * When work is completed it should be unsubscribed using {@link Scheduler.Worker#dispose()}. + * When work is completed, the {@code Worker} instance should be released + * by calling {@link Scheduler.Worker#dispose()} to avoid potential resource leaks in the + * underlying task-execution scheme. * <p> - * Work on a {@link Scheduler.Worker} is guaranteed to be sequential. + * Work on a {@link Scheduler.Worker} is guaranteed to be sequential and non-overlapping. * * @return a Worker representing a serial queue of actions to be executed */ @@ -77,7 +137,11 @@ public long now(@NonNull TimeUnit unit) { /** * Allows the Scheduler instance to start threads * and accept tasks on them. - * <p>Implementations should make sure the call is idempotent and thread-safe. + * <p> + * Implementations should make sure the call is idempotent, thread-safe and + * should not throw any {@code RuntimeException} if it doesn't support this + * functionality. + * * @since 2.0 */ public void start() { @@ -85,9 +149,13 @@ public void start() { } /** - * Instructs the Scheduler instance to stop threads - * and stop accepting tasks on any outstanding Workers. - * <p>Implementations should make sure the call is idempotent and thread-safe. + * Instructs the Scheduler instance to stop threads, + * stop accepting tasks on any outstanding {@link Worker} instances + * and clean up any associated resources with this Scheduler. + * <p> + * Implementations should make sure the call is idempotent, thread-safe and + * should not throw any {@code RuntimeException} if it doesn't support this + * functionality. * @since 2.0 */ public void shutdown() { @@ -95,11 +163,11 @@ public void shutdown() { } /** - * Schedules the given task on this scheduler non-delayed execution. + * Schedules the given task on this Scheduler without any time delay. * * <p> * This method is safe to be called from multiple threads but there are no - * ordering guarantees between tasks. + * ordering or non-overlapping guarantees between tasks. * * @param run the task to execute * @@ -112,7 +180,7 @@ public Disposable scheduleDirect(@NonNull Runnable run) { } /** - * Schedules the execution of the given task with the given delay amount. + * Schedules the execution of the given task with the given time delay. * * <p> * This method is safe to be called from multiple threads but there are no @@ -138,15 +206,16 @@ public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull Tim } /** - * Schedules a periodic execution of the given task with the given initial delay and period. + * Schedules a periodic execution of the given task with the given initial time delay and repeat period. * * <p> * This method is safe to be called from multiple threads but there are no * ordering guarantees between tasks. * * <p> - * The periodic execution is at a fixed rate, that is, the first execution will be after the initial - * delay, the second after initialDelay + period, the third after initialDelay + 2 * period, and so on. + * The periodic execution is at a fixed rate, that is, the first execution will be after the + * {@code initialDelay}, the second after {@code initialDelay + period}, the third after + * {@code initialDelay + 2 * period}, and so on. * * @param run the task to schedule * @param initialDelay the initial delay amount, non-positive values indicate non-delayed scheduling @@ -197,9 +266,9 @@ public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initial * <p> * Limit the amount concurrency two at a time without creating a new fix * size thread pool: - * + * * <pre> - * Scheduler limitScheduler = Schedulers.computation().when(workers -> { + * Scheduler limitScheduler = Schedulers.computation().when(workers -> { * // use merge max concurrent to limit the number of concurrent * // callbacks two at a time * return Completable.merge(Flowable.merge(workers), 2); @@ -215,30 +284,30 @@ public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initial * {@link Flowable#zip(org.reactivestreams.Publisher, org.reactivestreams.Publisher, io.reactivex.functions.BiFunction)} where * subscribing to the first {@link Flowable} could deadlock the * subscription to the second. - * + * * <pre> - * Scheduler limitScheduler = Schedulers.computation().when(workers -> { + * Scheduler limitScheduler = Schedulers.computation().when(workers -> { * // use merge max concurrent to limit the number of concurrent * // Flowables two at a time * return Completable.merge(Flowable.merge(workers, 2)); * }); * </pre> - * + * * Slowing down the rate to no more than than 1 a second. This suffers from * the same problem as the one above I could find an {@link Flowable} * operator that limits the rate without dropping the values (aka leaky * bucket algorithm). - * + * * <pre> - * Scheduler slowScheduler = Schedulers.computation().when(workers -> { + * Scheduler slowScheduler = Schedulers.computation().when(workers -> { * // use concatenate to make each worker happen one at a time. - * return Completable.concat(workers.map(actions -> { + * return Completable.concat(workers.map(actions -> { * // delay the starting of the next worker by 1 second. * return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS)); * })); * }); * </pre> - * + * * <p>History: 2.0.1 - experimental * @param <S> a Scheduler and a Subscription * @param combine the function that takes a two-level nested Flowable sequence of a Completable and returns @@ -253,13 +322,43 @@ public <S extends Scheduler & Disposable> S when(@NonNull Function<Flowable<Flow } /** - * Sequential Scheduler for executing actions on a single thread or event loop. + * Represents an isolated, sequential worker of a parent Scheduler for executing {@code Runnable} tasks on + * an underlying task-execution scheme (such as custom Threads, event loop, {@link java.util.concurrent.Executor Executor} or Actor system). + * <p> + * Disposing the {@link Worker} should cancel all outstanding work and allows resource cleanup. + * <p> + * The default implementations of {@link #schedule(Runnable)} and {@link #schedulePeriodically(Runnable, long, long, TimeUnit)} + * delegate to the abstract {@link #schedule(Runnable, long, TimeUnit)} method. Its implementation is encouraged to + * track the individual {@code Runnable} tasks while they are waiting to be executed (with or without delay) so that + * {@link #dispose()} can prevent their execution or potentially interrupt them if they are currently running. + * <p> + * The default implementation of the {@link #now(TimeUnit)} method returns current + * {@link System#currentTimeMillis()} value in the desired time unit. Custom {@code Worker} implementations can override this + * to provide specialized time accounting (such as virtual time to be advanced programmatically). + * Note that operators requiring a scheduler may rely on either of the {@code now()} calls provided by + * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically + * consistent source of the current time. + * <p> + * The default implementation of the {@link #schedulePeriodically(Runnable, long, long, TimeUnit)} method uses + * the {@link #schedule(Runnable, long, TimeUnit)} for scheduling the {@code Runnable} task periodically. + * The algorithm calculates the next absolute time when the task should run again and schedules this execution + * based on the relative time between it and {@link #now(TimeUnit)}. However, drifts or changes in the + * system clock would affect this calculation either by scheduling subsequent runs too frequently or too far apart. + * Therefore, the default implementation uses the {@link #clockDriftTolerance()} value (set via + * {@code rx2.scheduler.drift-tolerance} in minutes) to detect a drift in {@link #now(TimeUnit)} and + * re-adjust the absolute/relative time calculation accordingly. + * <p> + * If the {@code Worker} is disposed, the {@code schedule} methods + * should return the {@link io.reactivex.disposables.Disposables#disposed()} singleton instance indicating the disposed + * state to the caller. Since the {@link #dispose()} call can happen on any thread, the {@code schedule} implementations + * should make best effort to cancel tasks immediately after those tasks have been submitted to the + * underlying task-execution scheme if the dispose was detected after this submission. * <p> - * Disposing the {@link Worker} cancels all outstanding work and allows resource cleanup. + * All methods on the {@code Worker} class should be thread safe. */ public abstract static class Worker implements Disposable { /** - * Schedules a Runnable for execution without delay. + * Schedules a Runnable for execution without any time delay. * * <p>The default implementation delegates to {@link #schedule(Runnable, long, TimeUnit)}. * @@ -273,7 +372,8 @@ public Disposable schedule(@NonNull Runnable run) { } /** - * Schedules an Runnable for execution at some point in the future. + * Schedules an Runnable for execution at some point in the future specified by a time delay + * relative to the current time. * <p> * Note to implementors: non-positive {@code delayTime} should be regarded as non-delayed schedule, i.e., * as if the {@link #schedule(Runnable)} was called. @@ -281,7 +381,7 @@ public Disposable schedule(@NonNull Runnable run) { * @param run * the Runnable to schedule * @param delay - * time to wait before executing the action; non-positive values indicate an non-delayed + * time to "wait" before executing the action; non-positive values indicate an non-delayed * schedule * @param unit * the time unit of {@code delayTime} @@ -291,12 +391,20 @@ public Disposable schedule(@NonNull Runnable run) { public abstract Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit); /** - * Schedules a cancelable action to be executed periodically. This default implementation schedules - * recursively and waits for actions to complete (instead of potentially executing long-running actions - * concurrently). Each scheduler that can do periodic scheduling in a better way should override this. + * Schedules a periodic execution of the given task with the given initial time delay and repeat period. + * <p> + * The default implementation schedules and reschedules the {@code Runnable} task via the + * {@link #schedule(Runnable, long, TimeUnit)} + * method over and over and at a fixed rate, that is, the first execution will be after the + * {@code initialDelay}, the second after {@code initialDelay + period}, the third after + * {@code initialDelay + 2 * period}, and so on. * <p> * Note to implementors: non-positive {@code initialTime} and {@code period} should be regarded as * non-delayed scheduling of the first and any subsequent executions. + * In addition, a more specific {@code Worker} implementation should override this method + * if it can perform the periodic task execution with less overhead (such as by avoiding the + * creation of the wrapper and tracker objects upon each periodic invocation of the + * common {@link #schedule(Runnable, long, TimeUnit)} method). * * @param run * the Runnable to execute periodically @@ -347,7 +455,7 @@ public long now(@NonNull TimeUnit unit) { * Holds state and logic to calculate when the next delayed invocation * of this task has to happen (accounting for clock drifts). */ - final class PeriodicTask implements Runnable { + final class PeriodicTask implements Runnable, SchedulerRunnableIntrospection { @NonNull final Runnable decoratedRun; @NonNull @@ -393,15 +501,23 @@ public void run() { sd.replace(schedule(this, delay, TimeUnit.NANOSECONDS)); } } + + @Override + public Runnable getWrappedRunnable() { + return this.decoratedRun; + } } } - static class PeriodicDirectTask - implements Runnable, Disposable { + static final class PeriodicDirectTask + implements Disposable, Runnable, SchedulerRunnableIntrospection { + + @NonNull final Runnable run; + @NonNull final Worker worker; - @NonNull + volatile boolean disposed; PeriodicDirectTask(@NonNull Runnable run, @NonNull Worker worker) { @@ -432,15 +548,25 @@ public void dispose() { public boolean isDisposed() { return disposed; } + + @Override + public Runnable getWrappedRunnable() { + return run; + } } - static final class DisposeTask implements Runnable, Disposable { + static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection { + + @NonNull final Runnable decoratedRun; + + @NonNull final Worker w; + @Nullable Thread runner; - DisposeTask(Runnable decoratedRun, Worker w) { + DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) { this.decoratedRun = decoratedRun; this.w = w; } @@ -469,5 +595,10 @@ public void dispose() { public boolean isDisposed() { return w.isDisposed(); } + + @Override + public Runnable getWrappedRunnable() { + return this.decoratedRun; + } } } diff --git a/src/main/java/io/reactivex/Single.java b/src/main/java/io/reactivex/Single.java index 100da0d5b2..15ef6b6c14 100644 --- a/src/main/java/io/reactivex/Single.java +++ b/src/main/java/io/reactivex/Single.java @@ -28,6 +28,7 @@ import io.reactivex.internal.operators.completable.*; import io.reactivex.internal.operators.flowable.*; import io.reactivex.internal.operators.maybe.*; +import io.reactivex.internal.operators.mixed.*; import io.reactivex.internal.operators.observable.*; import io.reactivex.internal.operators.single.*; import io.reactivex.internal.util.*; @@ -36,32 +37,87 @@ import io.reactivex.schedulers.Schedulers; /** - * The Single class implements the Reactive Pattern for a single value response. - * See {@link Flowable} or {@link Observable} for the - * implementation of the Reactive Pattern for a stream or vector of values. + * The {@code Single} class implements the Reactive Pattern for a single value response. + * <p> + * {@code Single} behaves similarly to {@link Observable} except that it can only emit either a single successful + * value or an error (there is no "onComplete" notification as there is for an {@link Observable}). + * <p> + * The {@code Single} class implements the {@link SingleSource} base interface and the default consumer + * type it interacts with is the {@link SingleObserver} via the {@link #subscribe(SingleObserver)} method. * <p> - * {@code Single} behaves the same as {@link Observable} except that it can only emit either a single successful - * value, or an error (there is no "onComplete" notification as there is for {@link Observable}) + * The {@code Single} operates with the following sequential protocol: + * <pre> + * <code>onSubscribe (onSuccess | onError)?</code> + * </pre> * <p> - * Like an {@link Observable}, a {@code Single} is lazy, can be either "hot" or "cold", synchronous or - * asynchronous. + * Note that {@code onSuccess} and {@code onError} are mutually exclusive events; unlike {@code Observable}, + * {@code onSuccess} is never followed by {@code onError}. + * <p> + * Like {@code Observable}, a running {@code Single} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link SingleObserver#onSubscribe}. + * <p> + * Like an {@code Observable}, a {@code Single} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Single} instances returned by the methods of this class are <em>cold</em> + * and there is a standard <em>hot</em> implementation in the form of a subject: + * {@link io.reactivex.subjects.SingleSubject SingleSubject}. * <p> * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: * <p> * <img width="640" height="301" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.legend.png" alt=""> * <p> - * For more information see the <a href="http://reactivex.io/documentation/observable.html">ReactiveX + * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. + * <p> + * For more information see the <a href="http://reactivex.io/documentation/single.html">ReactiveX * documentation</a>. + * <p> + * Example: + * <pre><code> + * Disposable d = Single.just("Hello World") + * .delay(10, TimeUnit.SECONDS, Schedulers.io()) + * .subscribeWith(new DisposableSingleObserver<String>() { + * @Override + * public void onStart() { + * System.out.println("Started"); + * } + * + * @Override + * public void onSuccess(String value) { + * System.out.println("Success: " + value); + * } * + * @Override + * public void onError(Throwable error) { + * error.printStackTrace(); + * } + * }); + * + * Thread.sleep(5000); + * + * d.dispose(); + * </code></pre> + * <p> + * Note that by design, subscriptions via {@link #subscribe(SingleObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(SingleObserver)} method) and it is the + * responsibility of the implementor of the {@code SingleObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableSingleObserver DisposableSingleObserver} instance. + * For convenience, the {@link #subscribeWith(SingleObserver)} method is provided as well to + * allow working with a {@code SingleObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). * @param <T> * the type of the item emitted by the Single * @since 2.0 + * @see io.reactivex.observers.DisposableSingleObserver */ public abstract class Single<T> implements SingleSource<T> { /** - * Runs multiple Single sources and signals the events of the first one that signals (cancelling + * Runs multiple SingleSources and signals the events of the first one that signals (disposing * the rest). + * <p> + * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.amb.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code amb} does not operate by default on a particular {@link Scheduler}.</dd> @@ -73,6 +129,7 @@ public abstract class Single<T> implements SingleSource<T> { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> amb(final Iterable<? extends SingleSource<? extends T>> sources) { ObjectHelper.requireNonNull(sources, "sources is null"); @@ -80,8 +137,10 @@ public static <T> Single<T> amb(final Iterable<? extends SingleSource<? extends } /** - * Runs multiple Single sources and signals the events of the first one that signals (cancelling + * Runs multiple SingleSources and signals the events of the first one that signals (disposing * the rest). + * <p> + * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.ambArray.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambArray} does not operate by default on a particular {@link Scheduler}.</dd> @@ -106,8 +165,10 @@ public static <T> Single<T> ambArray(final SingleSource<? extends T>... sources) } /** - * Concatenate the single values, in a non-overlapping fashion, of the Single sources provided by + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by * an Iterable sequence. + * <p> + * <img width="640" height="319" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.i.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -120,6 +181,7 @@ public static <T> Single<T> ambArray(final SingleSource<? extends T>... sources) * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.FULL) public static <T> Flowable<T> concat(Iterable<? extends SingleSource<? extends T>> sources) { @@ -127,8 +189,10 @@ public static <T> Flowable<T> concat(Iterable<? extends SingleSource<? extends T } /** - * Concatenate the single values, in a non-overlapping fashion, of the Single sources provided by + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by * an Observable sequence. + * <p> + * <img width="640" height="319" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.o.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code concat} does not operate by default on a particular {@link Scheduler}.</dd> @@ -139,6 +203,7 @@ public static <T> Flowable<T> concat(Iterable<? extends SingleSource<? extends T * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> Observable<T> concat(ObservableSource<? extends SingleSource<? extends T>> sources) { @@ -147,8 +212,10 @@ public static <T> Observable<T> concat(ObservableSource<? extends SingleSource<? } /** - * Concatenate the single values, in a non-overlapping fashion, of the Single sources provided by + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by * a Publisher sequence. + * <p> + * <img width="640" height="308" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer @@ -162,6 +229,7 @@ public static <T> Observable<T> concat(ObservableSource<? extends SingleSource<? * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> concat(Publisher<? extends SingleSource<? extends T>> sources) { @@ -169,8 +237,10 @@ public static <T> Flowable<T> concat(Publisher<? extends SingleSource<? extends } /** - * Concatenate the single values, in a non-overlapping fashion, of the Single sources provided by + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by * a Publisher sequence and prefetched by the specified amount. + * <p> + * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.pn.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer @@ -185,6 +255,7 @@ public static <T> Flowable<T> concat(Publisher<? extends SingleSource<? extends * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -197,7 +268,7 @@ public static <T> Flowable<T> concat(Publisher<? extends SingleSource<? extends /** * Returns a Flowable that emits the items emitted by two Singles, one after the other. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.png" alt=""> + * <img width="640" height="366" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -214,6 +285,7 @@ public static <T> Flowable<T> concat(Publisher<? extends SingleSource<? extends * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -228,7 +300,7 @@ public static <T> Flowable<T> concat( /** * Returns a Flowable that emits the items emitted by three Singles, one after the other. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.png" alt=""> + * <img width="640" height="366" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.o3.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -247,6 +319,7 @@ public static <T> Flowable<T> concat( * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -263,7 +336,7 @@ public static <T> Flowable<T> concat( /** * Returns a Flowable that emits the items emitted by four Singles, one after the other. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.png" alt=""> + * <img width="640" height="362" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concat.o4.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -284,6 +357,7 @@ public static <T> Flowable<T> concat( * @see <a href="http://reactivex.io/documentation/operators/concat.html">ReactiveX operators documentation: Concat</a> */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -299,8 +373,10 @@ public static <T> Flowable<T> concat( } /** - * Concatenate the single values, in a non-overlapping fashion, of the Single sources provided in + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided in * an array. + * <p> + * <img width="640" height="319" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concatArray.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -313,6 +389,7 @@ public static <T> Flowable<T> concat( * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -321,7 +398,89 @@ public static <T> Flowable<T> concatArray(SingleSource<? extends T>... sources) } /** - * Provides an API (via a cold Completable) that bridges the reactive world with the callback-style world. + * Concatenates a sequence of SingleSource eagerly into a single stream of values. + * <p> + * <img width="640" height="257" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concatArrayEager.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source SingleSources. The operator buffers the value emitted by these SingleSources and then drains them + * in order, each one after the previous one completes. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator honors backpressure from downstream.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources a sequence of Single that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Flowable<T> concatArrayEager(SingleSource<? extends T>... sources) { + return Flowable.fromArray(sources).concatMapEager(SingleInternalHelper.<T>toFlowable()); + } + + /** + * Concatenates a Publisher sequence of SingleSources eagerly into a single stream of values. + * <p> + * <img width="640" height="307" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concatEager.p.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source Publishers as they are observed. The operator buffers the values emitted by these + * Publishers and then drains them in order, each one after the previous one completes. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>Backpressure is honored towards the downstream and the outer Publisher is + * expected to support backpressure. Violating this assumption, the operator will + * signal {@link io.reactivex.exceptions.MissingBackpressureException}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Flowable<T> concatEager(Publisher<? extends SingleSource<? extends T>> sources) { + return Flowable.fromPublisher(sources).concatMapEager(SingleInternalHelper.<T>toFlowable()); + } + + /** + * Concatenates a sequence of SingleSources eagerly into a single stream of values. + * <p> + * <img width="640" height="319" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.concatEager.i.png" alt=""> + * <p> + * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source SingleSources. The operator buffers the values emitted by these SingleSources and then drains them + * in order, each one after the previous one completes. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>Backpressure is honored towards the downstream.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This method does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param <T> the value type + * @param sources a sequence of SingleSource that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Flowable<T> concatEager(Iterable<? extends SingleSource<? extends T>> sources) { + return Flowable.fromIterable(sources).concatMapEager(SingleInternalHelper.<T>toFlowable()); + } + + /** + * Provides an API (via a cold Single) that bridges the reactive world with the callback-style world. + * <p> + * <img width="640" height="454" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.create.png" alt=""> * <p> * Example: * <pre><code> @@ -344,7 +503,6 @@ public static <T> Flowable<T> concatArray(SingleSource<? extends T>... sources) * * }); * </code></pre> - * <p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code create} does not operate by default on a particular {@link Scheduler}.</dd> @@ -356,6 +514,7 @@ public static <T> Flowable<T> concatArray(SingleSource<? extends T>... sources) * @see Cancellable */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> create(SingleOnSubscribe<T> source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -363,18 +522,21 @@ public static <T> Single<T> create(SingleOnSubscribe<T> source) { } /** - * Calls a Callable for each individual SingleObserver to return the actual Single source to + * Calls a {@link Callable} for each individual {@link SingleObserver} to return the actual {@link SingleSource} to * be subscribed to. + * <p> + * <img width="640" height="515" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.defer.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code defer} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * @param <T> the value type - * @param singleSupplier the Callable that is called for each individual SingleObserver and + * @param singleSupplier the {@code Callable} that is called for each individual {@code SingleObserver} and * returns a SingleSource instance to subscribe to * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> defer(final Callable<? extends SingleSource<? extends T>> singleSupplier) { ObjectHelper.requireNonNull(singleSupplier, "singleSupplier is null"); @@ -383,6 +545,8 @@ public static <T> Single<T> defer(final Callable<? extends SingleSource<? extend /** * Signals a Throwable returned by the callback function for each individual SingleObserver. + * <p> + * <img width="640" height="283" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.error.c.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code error} does not operate by default on a particular {@link Scheduler}.</dd> @@ -393,6 +557,7 @@ public static <T> Single<T> defer(final Callable<? extends SingleSource<? extend * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> error(final Callable<? extends Throwable> errorSupplier) { ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); @@ -403,7 +568,7 @@ public static <T> Single<T> error(final Callable<? extends Throwable> errorSuppl * Returns a Single that invokes a subscriber's {@link SingleObserver#onError onError} method when the * subscriber subscribes to it. * <p> - * <img width="640" height="190" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.error.png" alt=""> + * <img width="640" height="283" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.error.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code error} does not operate by default on a particular {@link Scheduler}.</dd> @@ -418,9 +583,10 @@ public static <T> Single<T> error(final Callable<? extends Throwable> errorSuppl * @see <a href="http://reactivex.io/documentation/operators/empty-never-throw.html">ReactiveX operators documentation: Throw</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> error(final Throwable exception) { - ObjectHelper.requireNonNull(exception, "error is null"); + ObjectHelper.requireNonNull(exception, "exception is null"); return error(Functions.justCallable(exception)); } @@ -430,9 +596,18 @@ public static <T> Single<T> error(final Throwable exception) { * Allows you to defer execution of passed function until SingleObserver subscribes to the {@link Single}. * It makes passed function "lazy". * Result of the function invocation will be emitted by the {@link Single}. + * <p> + * <img width="640" height="467" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.fromCallable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromCallable} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd> If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link SingleObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Single} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * </dd> * </dl> * * @param callable @@ -442,6 +617,7 @@ public static <T> Single<T> error(final Throwable exception) { * @return a {@link Single} whose {@link SingleObserver}s' subscriptions trigger an invocation of the given function. */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> fromCallable(final Callable<? extends T> callable) { ObjectHelper.requireNonNull(callable, "callable is null"); @@ -555,7 +731,7 @@ public static <T> Single<T> fromFuture(Future<? extends T> future, long timeout, * {@code from} method. * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param future @@ -579,6 +755,19 @@ public static <T> Single<T> fromFuture(Future<? extends T> future, Scheduler sch * Wraps a specific Publisher into a Single and signals its single element or error. * <p>If the source Publisher is empty, a NoSuchElementException is signalled. If * the source has more than one element, an IndexOutOfBoundsException is signalled. + * <p> + * The {@link Publisher} must follow the + * <a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams">Reactive Streams specification</a>. + * Violating the specification may result in undefined behavior. + * <p> + * If possible, use {@link #create(SingleOnSubscribe)} to create a + * source-like {@code Single} instead. + * <p> + * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. + * <p> + * <img width="640" height="322" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.fromPublisher.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The {@code publisher} is consumed in an unbounded fashion but will be cancelled @@ -589,9 +778,11 @@ public static <T> Single<T> fromFuture(Future<? extends T> future, Scheduler sch * @param <T> the value type * @param publisher the source Publisher instance, not null * @return the new Single instance + * @see #create(SingleOnSubscribe) */ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> fromPublisher(final Publisher<? extends T> publisher) { ObjectHelper.requireNonNull(publisher, "publisher is null"); @@ -603,6 +794,7 @@ public static <T> Single<T> fromPublisher(final Publisher<? extends T> publisher * <p>If the ObservableSource is empty, a NoSuchElementException is signalled. * If the source has more than one element, an IndexOutOfBoundsException is signalled. * <p> + * <img width="640" height="343" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.fromObservable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code fromObservable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -614,6 +806,7 @@ public static <T> Single<T> fromPublisher(final Publisher<? extends T> publisher * @return the new Single instance */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> fromObservable(ObservableSource<? extends T> observableSource) { ObjectHelper.requireNonNull(observableSource, "observableSource is null"); @@ -641,26 +834,44 @@ public static <T> Single<T> fromObservable(ObservableSource<? extends T> observa */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @NonNull public static <T> Single<T> just(final T item) { - ObjectHelper.requireNonNull(item, "value is null"); + ObjectHelper.requireNonNull(item, "item is null"); return RxJavaPlugins.onAssembly(new SingleJust<T>(item)); } /** * Merges an Iterable sequence of SingleSource instances into a single Flowable sequence, * running all SingleSources at once. + * <p> + * <img width="640" height="319" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.i.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the Iterable sequence of SingleSource sources * @return the new Flowable instance * @since 2.0 + * @see #mergeDelayError(Iterable) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static <T> Flowable<T> merge(Iterable<? extends SingleSource<? extends T>> sources) { @@ -670,18 +881,35 @@ public static <T> Flowable<T> merge(Iterable<? extends SingleSource<? extends T> /** * Merges a Flowable sequence of SingleSource instances into a single Flowable sequence, * running all SingleSources at once. + * <p> + * <img width="640" height="307" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + * </dd> * </dl> * @param <T> the common and resulting value type * @param sources the Flowable sequence of SingleSource sources * @return the new Flowable instance + * @see #mergeDelayError(Publisher) * @since 2.0 */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -694,11 +922,15 @@ public static <T> Flowable<T> merge(Publisher<? extends SingleSource<? extends T * Flattens a {@code Single} that emits a {@code Single} into a single {@code Single} that emits the item * emitted by the nested {@code Single}, without any transformation. * <p> - * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.oo.png" alt=""> - * <p> + * <img width="640" height="412" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.oo.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dd>The resulting {@code Single} emits the outer source's or the inner {@code SingleSource}'s {@code Throwable} as is. + * Unlike the other {@code merge()} operators, this operator won't and can't produce a {@code CompositeException} because there is + * only one possibility for the outer or the inner {@code SingleSource} to emit an {@code onError} signal. + * Therefore, there is no need for a {@code mergeDelayError(SingleSource<SingleSource<T>>)} operator. + * </dd> * </dl> * * @param <T> the value type of the sources and the output @@ -709,6 +941,7 @@ public static <T> Flowable<T> merge(Publisher<? extends SingleSource<? extends T * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T> Single<T> merge(SingleSource<? extends SingleSource<? extends T>> source) { @@ -719,7 +952,7 @@ public static <T> Single<T> merge(SingleSource<? extends SingleSource<? extends /** * Flattens two Singles into a single Flowable, without any transformation. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.png" alt=""> + * <img width="640" height="414" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.png" alt=""> * <p> * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by * using the {@code merge} method. @@ -728,17 +961,32 @@ public static <T> Single<T> merge(SingleSource<? extends SingleSource<? extends * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type * @param source1 - * a Single to be merged + * a SingleSource to be merged * @param source2 - * a Single to be merged + * a SingleSource to be merged * @return a Flowable that emits all of the items emitted by the source Singles * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(SingleSource, SingleSource) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -753,7 +1001,7 @@ public static <T> Flowable<T> merge( /** * Flattens three Singles into a single Flowable, without any transformation. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.png" alt=""> + * <img width="640" height="366" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.o3.png" alt=""> * <p> * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using * the {@code merge} method. @@ -762,19 +1010,34 @@ public static <T> Flowable<T> merge( * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type * @param source1 - * a Single to be merged + * a SingleSource to be merged * @param source2 - * a Single to be merged + * a SingleSource to be merged * @param source3 - * a Single to be merged + * a SingleSource to be merged * @return a Flowable that emits all of the items emitted by the source Singles * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(SingleSource, SingleSource, SingleSource) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -791,7 +1054,7 @@ public static <T> Flowable<T> merge( /** * Flattens four Singles into a single Flowable, without any transformation. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.png" alt=""> + * <img width="640" height="362" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.o4.png" alt=""> * <p> * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using * the {@code merge} method. @@ -800,21 +1063,36 @@ public static <T> Flowable<T> merge( * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> * <dt><b>Scheduler:</b></dt> * <dd>{@code merge} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource, SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + * </dd> * </dl> * * @param <T> the common value type * @param source1 - * a Single to be merged + * a SingleSource to be merged * @param source2 - * a Single to be merged + * a SingleSource to be merged * @param source3 - * a Single to be merged + * a SingleSource to be merged * @param source4 - * a Single to be merged + * a SingleSource to be merged * @return a Flowable that emits all of the items emitted by the source Singles * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #mergeDelayError(SingleSource, SingleSource, SingleSource, SingleSource) */ @CheckReturnValue + @NonNull @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") @@ -829,8 +1107,189 @@ public static <T> Flowable<T> merge( return merge(Flowable.fromArray(source1, source2, source3, source4)); } + /** + * Merges an Iterable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once and delaying any error(s) until all sources succeed or fail. + * <p> + * <img width="640" height="469" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeDelayError.i.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common and resulting value type + * @param sources the Iterable sequence of SingleSource sources + * @return the new Flowable instance + * @see #merge(Iterable) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static <T> Flowable<T> mergeDelayError(Iterable<? extends SingleSource<? extends T>> sources) { + return mergeDelayError(Flowable.fromIterable(sources)); + } + + /** + * Merges a Flowable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once and delaying any error(s) until all sources succeed or fail. + * <p> + * <img width="640" height="356" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeDelayError.p.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common and resulting value type + * @param sources the Flowable sequence of SingleSource sources + * @return the new Flowable instance + * @see #merge(Publisher) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <T> Flowable<T> mergeDelayError(Publisher<? extends SingleSource<? extends T>> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, SingleInternalHelper.toFlowable(), true, Integer.MAX_VALUE, Flowable.bufferSize())); + } + + /** + * Flattens two Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + * <p> + * <img width="640" height="554" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeDelayError.2.png" alt=""> + * <p> + * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by + * using the {@code mergeDelayError} method. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #merge(SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static <T> Flowable<T> mergeDelayError( + SingleSource<? extends T> source1, SingleSource<? extends T> source2 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2)); + } + + /** + * Flattens three Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + * <p> + * <img width="640" height="496" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeDelayError.3.png" alt=""> + * <p> + * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code mergeDelayError} method. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #merge(SingleSource, SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static <T> Flowable<T> mergeDelayError( + SingleSource<? extends T> source1, SingleSource<? extends T> source2, + SingleSource<? extends T> source3 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2, source3)); + } + + /** + * Flattens four Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + * <p> + * <img width="640" height="509" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeDelayError.4.png" alt=""> + * <p> + * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code mergeDelayError} method. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.9 - experimental + * @param <T> the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @param source4 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> + * @see #merge(SingleSource, SingleSource, SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static <T> Flowable<T> mergeDelayError( + SingleSource<? extends T> source1, SingleSource<? extends T> source2, + SingleSource<? extends T> source3, SingleSource<? extends T> source4 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2, source3, source4)); + } + /** * Returns a singleton instance of a never-signalling Single (only calls onSubscribe). + * <p> + * <img width="640" height="244" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.never.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code never} does not operate by default on a particular {@link Scheduler}.</dd> @@ -848,6 +1307,8 @@ public static <T> Single<T> never() { /** * Signals success with 0L value after the given delay for each SingleObserver. + * <p> + * <img width="640" height="292" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timer.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timer} operates by default on the {@code computation} {@link Scheduler}.</dd> @@ -865,6 +1326,8 @@ public static Single<Long> timer(long delay, TimeUnit unit) { /** * Signals success with 0L value after the given delay for each SingleObserver. + * <p> + * <img width="640" height="292" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timer.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>you specify the {@link Scheduler} to signal on.</dd> @@ -873,9 +1336,13 @@ public static Single<Long> timer(long delay, TimeUnit unit) { * @param unit the time unit of the delay * @param scheduler the scheduler where the single 0L will be emitted * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public static Single<Long> timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { ObjectHelper.requireNonNull(unit, "unit is null"); @@ -885,6 +1352,8 @@ public static Single<Long> timer(final long delay, final TimeUnit unit, final Sc /** * Compares two SingleSources and emits true if they emit the same value (compared via Object.equals). + * <p> + * <img width="640" height="465" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.equals.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code equals} does not operate by default on a particular {@link Scheduler}.</dd> @@ -896,6 +1365,7 @@ public static Single<Long> timer(final long delay, final TimeUnit unit, final Sc * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<Boolean> equals(final SingleSource<? extends T> first, final SingleSource<? extends T> second) { // NOPMD ObjectHelper.requireNonNull(first, "first is null"); @@ -906,6 +1376,8 @@ public static <T> Single<Boolean> equals(final SingleSource<? extends T> first, /** * <strong>Advanced use only:</strong> creates a Single instance without * any safeguards by using a callback that is called with a SingleObserver. + * <p> + * <img width="640" height="261" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.unsafeCreate.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -919,6 +1391,7 @@ public static <T> Single<Boolean> equals(final SingleSource<? extends T> first, * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> unsafeCreate(SingleSource<T> onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); @@ -931,6 +1404,8 @@ public static <T> Single<T> unsafeCreate(SingleSource<T> onSubscribe) { /** * Allows using and disposing a resource while running a SingleSource instance generated from * that resource (similar to a try-with-resources). + * <p> + * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.using.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code using} does not operate by default on a particular {@link Scheduler}.</dd> @@ -943,7 +1418,7 @@ public static <T> Single<T> unsafeCreate(SingleSource<T> onSubscribe) { * to be run by the operator * @param disposer the consumer of the generated resource that is called exactly once for * that particular resource when the generated SingleSource terminates - * (successfully or with an error) or gets cancelled. + * (successfully or with an error) or gets disposed. * @return the new Single instance * @since 2.0 */ @@ -958,6 +1433,8 @@ public static <T, U> Single<T> using(Callable<U> resourceSupplier, /** * Allows using and disposing a resource while running a SingleSource instance generated from * that resource (similar to a try-with-resources). + * <p> + * <img width="640" height="325" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.using.b.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code using} does not operate by default on a particular {@link Scheduler}.</dd> @@ -970,7 +1447,7 @@ public static <T, U> Single<T> using(Callable<U> resourceSupplier, * to be run by the operator * @param disposer the consumer of the generated resource that is called exactly once for * that particular resource when the generated SingleSource terminates - * (successfully or with an error) or gets cancelled. + * (successfully or with an error) or gets disposed. * @param eager * if true, the disposer is called before the terminal event is signalled * if false, the disposer is called after the terminal event is delivered to downstream @@ -978,6 +1455,7 @@ public static <T, U> Single<T> using(Callable<U> resourceSupplier, * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, U> Single<T> using( final Callable<U> resourceSupplier, @@ -994,6 +1472,8 @@ public static <T, U> Single<T> using( /** * Wraps a SingleSource instance into a new Single instance if not already a Single * instance. + * <p> + * <img width="640" height="350" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.wrap.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code wrap} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1003,6 +1483,7 @@ public static <T, U> Single<T> using( * @return the Single wrapper or the source cast to Single (if possible) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T> Single<T> wrap(SingleSource<T> source) { ObjectHelper.requireNonNull(source, "source is null"); @@ -1017,6 +1498,8 @@ public static <T> Single<T> wrap(SingleSource<T> source) { * value and calls a zipper function with an array of these values to return a result * to be emitted to downstream. * <p> + * If the {@code Iterable} of {@link SingleSource}s is empty a {@link NoSuchElementException} error is signalled after subscription. + * <p> * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. @@ -1024,7 +1507,7 @@ public static <T> Single<T> wrap(SingleSource<T> source) { * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <p> - * If any of the SingleSources signal an error, all other SingleSources get cancelled and the + * If any of the SingleSources signal an error, all other SingleSources get disposed and the * error emitted to downstream immediately. * <dl> * <dt><b>Scheduler:</b></dt> @@ -1032,13 +1515,15 @@ public static <T> Single<T> wrap(SingleSource<T> source) { * </dl> * @param <T> the common value type * @param <R> the result value type - * @param sources the Iterable sequence of SingleSource instances + * @param sources the Iterable sequence of SingleSource instances. An empty sequence will result in an + * {@code onError} signal of {@link NoSuchElementException}. * @param zipper the function that receives an array with values from each SingleSource * and should return a value to be emitted to downstream * @return the new Single instance * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Single<R> zip(final Iterable<? extends SingleSource<? extends T>> sources, Function<? super Object[], ? extends R> zipper) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1070,6 +1555,7 @@ public static <T, R> Single<R> zip(final Iterable<? extends SingleSource<? exten * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, R> Single<R> zip( @@ -1108,6 +1594,7 @@ public static <T1, T2, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, R> Single<R> zip( @@ -1151,6 +1638,7 @@ public static <T1, T2, T3, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, R> Single<R> zip( @@ -1198,6 +1686,7 @@ public static <T1, T2, T3, T4, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, T5, R> Single<R> zip( @@ -1250,6 +1739,7 @@ public static <T1, T2, T3, T4, T5, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, T5, T6, R> Single<R> zip( @@ -1306,6 +1796,7 @@ public static <T1, T2, T3, T4, T5, T6, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, T5, T6, T7, R> Single<R> zip( @@ -1367,6 +1858,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Single<R> zip( @@ -1432,6 +1924,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, R> Single<R> zip( * @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Single<R> zip( @@ -1459,6 +1952,8 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Single<R> zip( * value and calls a zipper function with an array of these values to return a result * to be emitted to downstream. * <p> + * If the array of {@link SingleSource}s is empty a {@link NoSuchElementException} error is signalled immediately. + * <p> * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a * {@code Function<Integer[], R>} passed to the method would trigger a {@code ClassCastException}. @@ -1466,7 +1961,7 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Single<R> zip( * <p> * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> * <p> - * If any of the SingleSources signal an error, all other SingleSources get cancelled and the + * If any of the SingleSources signal an error, all other SingleSources get disposed and the * error emitted to downstream immediately. * <dl> * <dt><b>Scheduler:</b></dt> @@ -1474,13 +1969,15 @@ public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Single<R> zip( * </dl> * @param <T> the common value type * @param <R> the result value type - * @param sources the array of SingleSource instances + * @param sources the array of SingleSource instances. An empty sequence will result in an + * {@code onError} signal of {@link NoSuchElementException}. * @param zipper the function that receives an array with values from each SingleSource * and should return a value to be emitted to downstream * @return the new Single instance * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> zipper, SingleSource<? extends T>... sources) { ObjectHelper.requireNonNull(zipper, "zipper is null"); @@ -1493,6 +1990,8 @@ public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> /** * Signals the event of this or the other SingleSource whichever signals first. + * <p> + * <img width="640" height="463" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.ambWith.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code ambWith} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1503,6 +2002,7 @@ public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) @SuppressWarnings("unchecked") public final Single<T> ambWith(SingleSource<? extends T> other) { @@ -1510,9 +2010,34 @@ public final Single<T> ambWith(SingleSource<? extends T> other) { return ambArray(this, other); } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * <img width="640" height="553" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.as.png" alt=""> + * <p> + * This allows fluent conversion to any other type. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code as} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current Single instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final <R> R as(@NonNull SingleConverter<T, ? extends R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + /** * Hides the identity of the current Single, including the Disposable that is sent * to the downstream via {@code onSubscribe()}. + * <p> + * <img width="640" height="458" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.hide.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code hide} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1529,6 +2054,8 @@ public final Single<T> hide() { /** * Transform a Single by applying a particular Transformer function to it. * <p> + * <img width="640" height="612" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.compose.png" alt=""> + * <p> * This method operates on the Single itself whereas {@link #lift} operates on the Single's SingleObservers. * <p> * If the operator you are creating is designed to act on the individual item emitted by a Single, use @@ -1554,6 +2081,7 @@ public final <R> Single<R> compose(SingleTransformer<? super T, ? extends R> tra /** * Stores the success value or exception from the current Single and replays it to late SingleObservers. * <p> + * <img width="640" height="363" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.cache.png" alt=""> * The returned Single subscribes to the current Single when the first SingleObserver subscribes. * <dl> * <dt><b>Scheduler:</b></dt> @@ -1572,6 +2100,8 @@ public final Single<T> cache() { /** * Casts the success value of the current Single into the target type or signals a * ClassCastException if not compatible. + * <p> + * <img width="640" height="393" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.cast.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code cast} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1582,6 +2112,7 @@ public final Single<T> cache() { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<U> cast(final Class<? extends U> clazz) { ObjectHelper.requireNonNull(clazz, "clazz is null"); @@ -1614,14 +2145,16 @@ public final Flowable<T> concatWith(SingleSource<? extends T> other) { } /** - * Delays the emission of the success or error signal from the current Single by - * the specified amount. + * Delays the emission of the success signal from the current Single by the specified amount. + * An error signal will not be delayed. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delay.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delay} operates by default on the {@code computation} {@link Scheduler}.</dd> * </dl> * - * @param time the time amount to delay the signals + * @param time the amount of time the success signal should be delayed for * @param unit the time unit * @return the new Single instance * @since 2.0 @@ -1629,33 +2162,88 @@ public final Flowable<T> concatWith(SingleSource<? extends T> other) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Single<T> delay(long time, TimeUnit unit) { - return delay(time, unit, Schedulers.computation()); + return delay(time, unit, Schedulers.computation(), false); + } + + /** + * Delays the emission of the success or error signal from the current Single by the specified amount. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delay.e.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code delay} operates by default on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.5 - experimental + * @param time the amount of time the success or error signal should be delayed for + * @param unit the time unit + * @param delayError if true, both success and error signals are delayed. if false, only success signals are delayed. + * @return the new Single instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single<T> delay(long time, TimeUnit unit, boolean delayError) { + return delay(time, unit, Schedulers.computation(), delayError); } /** * Delays the emission of the success signal from the current Single by the specified amount. + * An error signal will not be delayed. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delay.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>you specify the {@link Scheduler} where the non-blocking wait and emission happens</dd> * </dl> * - * @param time the time amount to delay the emission of the success signal + * @param time the amount of time the success signal should be delayed for * @param unit the time unit * @param scheduler the target scheduler to use for the non-blocking wait and emission * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null * @since 2.0 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single<T> delay(final long time, final TimeUnit unit, final Scheduler scheduler) { + return delay(time, unit, scheduler, false); + } + + /** + * Delays the emission of the success or error signal from the current Single by the specified amount. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delay.se.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>you specify the {@link Scheduler} where the non-blocking wait and emission happens</dd> + * </dl> + * <p>History: 2.1.5 - experimental + * @param time the amount of time the success or error signal should be delayed for + * @param unit the time unit + * @param scheduler the target scheduler to use for the non-blocking wait and emission + * @param delayError if true, both success and error signals are delayed. if false, only success signals are delayed. + * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single<T> delay(final long time, final TimeUnit unit, final Scheduler scheduler, boolean delayError) { ObjectHelper.requireNonNull(unit, "unit is null"); ObjectHelper.requireNonNull(scheduler, "scheduler is null"); - return RxJavaPlugins.onAssembly(new SingleDelay<T>(this, time, unit, scheduler)); + return RxJavaPlugins.onAssembly(new SingleDelay<T>(this, time, unit, scheduler, delayError)); } /** * Delays the actual subscription to the current Single until the given other CompletableSource * completes. + * <p> + * <img width="640" height="309" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.c.png" alt=""> * <p>If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. * <dl> @@ -1668,6 +2256,7 @@ public final Single<T> delay(final long time, final TimeUnit unit, final Schedul * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> delaySubscription(CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1677,6 +2266,8 @@ public final Single<T> delaySubscription(CompletableSource other) { /** * Delays the actual subscription to the current Single until the given other SingleSource * signals success. + * <p> + * <img width="640" height="309" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.s.png" alt=""> * <p>If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. * <dl> @@ -1690,6 +2281,7 @@ public final Single<T> delaySubscription(CompletableSource other) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<T> delaySubscription(SingleSource<U> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1699,6 +2291,8 @@ public final <U> Single<T> delaySubscription(SingleSource<U> other) { /** * Delays the actual subscription to the current Single until the given other ObservableSource * signals its first value or completes. + * <p> + * <img width="640" height="214" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.o.png" alt=""> * <p>If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. * <dl> @@ -1712,6 +2306,7 @@ public final <U> Single<T> delaySubscription(SingleSource<U> other) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<T> delaySubscription(ObservableSource<U> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1721,6 +2316,8 @@ public final <U> Single<T> delaySubscription(ObservableSource<U> other) { /** * Delays the actual subscription to the current Single until the given other Publisher * signals its first value or completes. + * <p> + * <img width="640" height="214" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.p.png" alt=""> * <p>If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. * <p>The other source is consumed in an unbounded manner (requesting Long.MAX_VALUE from it). @@ -1739,6 +2336,7 @@ public final <U> Single<T> delaySubscription(ObservableSource<U> other) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Single<T> delaySubscription(Publisher<U> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -1747,12 +2345,13 @@ public final <U> Single<T> delaySubscription(Publisher<U> other) { /** * Delays the actual subscription to the current Single until the given time delay elapsed. + * <p> + * <img width="640" height="472" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.t.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delaySubscription} does by default subscribe to the current Single * on the {@code computation} {@link Scheduler} after the delay.</dd> * </dl> - * @param <U> the element type of the other source * @param time the time amount to wait with the subscription * @param unit the time unit of the waiting * @return the new Single instance @@ -1760,18 +2359,19 @@ public final <U> Single<T> delaySubscription(Publisher<U> other) { */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.COMPUTATION) - public final <U> Single<T> delaySubscription(long time, TimeUnit unit) { + public final Single<T> delaySubscription(long time, TimeUnit unit) { return delaySubscription(time, unit, Schedulers.computation()); } /** * Delays the actual subscription to the current Single until the given time delay elapsed. + * <p> + * <img width="640" height="420" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.delaySubscription.ts.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code delaySubscription} does by default subscribe to the current Single * on the {@link Scheduler} you provided, after the delay.</dd> * </dl> - * @param <U> the element type of the other source * @param time the time amount to wait with the subscription * @param unit the time unit of the waiting * @param scheduler the scheduler to wait on and subscribe on to the current Single @@ -1780,13 +2380,56 @@ public final <U> Single<T> delaySubscription(long time, TimeUnit unit) { */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) - public final <U> Single<T> delaySubscription(long time, TimeUnit unit, Scheduler scheduler) { + public final Single<T> delaySubscription(long time, TimeUnit unit, Scheduler scheduler) { return delaySubscription(Observable.timer(time, unit, scheduler)); } + /** + * Maps the {@link Notification} success value of this Single back into normal + * {@code onSuccess}, {@code onError} or {@code onComplete} signals as a + * {@link Maybe} source. + * <p> + * <img width="640" height="341" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.dematerialize.png" alt=""> + * <p> + * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification<T>}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code dematerialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p> + * Example: + * <pre><code> + * Single.just(Notification.createOnNext(1)) + * .dematerialize(notification -> notification) + * .test() + * .assertResult(1); + * </code></pre> + * @param <R> the result type + * @param selector the function called with the success item and should + * return a {@link Notification} instance. + * @return the new Maybe instance + * @since 2.2.4 - experimental + * @see #materialize() + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @Experimental + public final <R> Maybe<R> dematerialize(Function<? super T, Notification<R>> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new SingleDematerialize<T, R>(this, selector)); + } + /** * Calls the specified consumer with the success item after this item has been emitted to the downstream. - * <p>Note that the {@code doAfterSuccess} action is shared between subscriptions and as such + * <p> + * <img width="640" height="460" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doAfterSuccess.png" alt=""> + * <p> + * Note that the {@code doAfterSuccess} action is shared between subscriptions and as such * should be thread-safe. * <dl> * <dt><b>Scheduler:</b></dt> @@ -1798,18 +2441,21 @@ public final <U> Single<T> delaySubscription(long time, TimeUnit unit, Scheduler * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doAfterSuccess(Consumer<? super T> onAfterSuccess) { - ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); return RxJavaPlugins.onAssembly(new SingleDoAfterSuccess<T>(this, onAfterSuccess)); } /** * Registers an {@link Action} to be called after this Single invokes either onSuccess or onError. - * * <p>Note that the {@code doAfterTerminate} action is shared between subscriptions and as such - * should be thread-safe.</p> * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doAfterTerminate.png" alt=""> + * <img width="640" height="460" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doAfterTerminate.png" alt=""> + * <p> + * Note that the {@code doAfterTerminate} action is shared between subscriptions and as such + * should be thread-safe.</p> + * * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1824,6 +2470,7 @@ public final Single<T> doAfterSuccess(Consumer<? super T> onAfterSuccess) { * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doAfterTerminate(Action onAfterTerminate) { ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); @@ -1837,16 +2484,20 @@ public final Single<T> doAfterTerminate(Action onAfterTerminate) { * is executed once per subscription. * <p>Note that the {@code onFinally} action is shared between subscriptions and as such * should be thread-safe. + * <p> + * <img width="640" height="291" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doFinally.png" alt=""> + * </p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doFinally} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> * <p>History: 2.0.1 - experimental - * @param onFinally the action called when this Single terminates or gets cancelled + * @param onFinally the action called when this Single terminates or gets disposed * @return the new Single instance * @since 2.1 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); @@ -1856,6 +2507,9 @@ public final Single<T> doFinally(Action onFinally) { /** * Calls the shared consumer with the Disposable sent through the onSubscribe for each * SingleObserver that subscribes to the current Single. + * <p> + * <img width="640" height="347" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doOnSubscribe.png" alt=""> + * </p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1865,15 +2519,46 @@ public final Single<T> doFinally(Action onFinally) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doOnSubscribe(final Consumer<? super Disposable> onSubscribe) { ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); return RxJavaPlugins.onAssembly(new SingleDoOnSubscribe<T>(this, onSubscribe)); } + /** + * Returns a Single instance that calls the given onTerminate callback + * just before this Single completes normally or with an exception. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/doOnTerminate.png" alt=""> + * <p> + * This differs from {@code doAfterTerminate} in that this happens <em>before</em> the {@code onSuccess} or + * {@code onError} notification. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @param onTerminate the action to invoke when the consumer calls {@code onSuccess} or {@code onError} + * @return the new Single instance + * @see <a href="http://reactivex.io/documentation/operators/do.html">ReactiveX operators documentation: Do</a> + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<T> doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnTerminate<T>(this, onTerminate)); + } + /** * Calls the shared consumer with the success value sent via onSuccess for each * SingleObserver that subscribes to the current Single. + * <p> + * <img width="640" height="347" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doOnSuccess.2.png" alt=""> + * </p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnSuccess} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1883,6 +2568,7 @@ public final Single<T> doOnSubscribe(final Consumer<? super Disposable> onSubscr * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doOnSuccess(final Consumer<? super T> onSuccess) { ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); @@ -1892,6 +2578,8 @@ public final Single<T> doOnSuccess(final Consumer<? super T> onSuccess) { /** * Calls the shared consumer with the error sent via onError or the value * via onSuccess for each SingleObserver that subscribes to the current Single. + * <p> + * <img width="640" height="264" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doOnEvent.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1901,6 +2589,7 @@ public final Single<T> doOnSuccess(final Consumer<? super T> onSuccess) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doOnEvent(final BiConsumer<? super T, ? super Throwable> onEvent) { ObjectHelper.requireNonNull(onEvent, "onEvent is null"); @@ -1910,6 +2599,9 @@ public final Single<T> doOnEvent(final BiConsumer<? super T, ? super Throwable> /** * Calls the shared consumer with the error sent via onError for each * SingleObserver that subscribes to the current Single. + * <p> + * <img width="640" height="349" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doOnError.2.png" alt=""> + * </p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnError} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1919,6 +2611,7 @@ public final Single<T> doOnEvent(final BiConsumer<? super T, ? super Throwable> * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doOnError(final Consumer<? super Throwable> onError) { ObjectHelper.requireNonNull(onError, "onError is null"); @@ -1928,6 +2621,9 @@ public final Single<T> doOnError(final Consumer<? super Throwable> onError) { /** * Calls the shared {@code Action} if a SingleObserver subscribed to the current Single * disposes the common Disposable it received via onSubscribe. + * <p> + * <img width="640" height="332" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.doOnDispose.png" alt=""> + * </p> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1938,6 +2634,7 @@ public final Single<T> doOnError(final Consumer<? super Throwable> onError) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> doOnDispose(final Action onDispose) { ObjectHelper.requireNonNull(onDispose, "onDispose is null"); @@ -1948,7 +2645,7 @@ public final Single<T> doOnDispose(final Action onDispose) { * Filters the success item of the Single via a predicate function and emitting it if the predicate * returns true, completing otherwise. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/filter.png" alt=""> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.filter.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code filter} does not operate by default on a particular {@link Scheduler}.</dd> @@ -1962,6 +2659,7 @@ public final Single<T> doOnDispose(final Action onDispose) { * @see <a href="http://reactivex.io/documentation/operators/filter.html">ReactiveX operators documentation: Filter</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe<T> filter(Predicate<? super T> predicate) { ObjectHelper.requireNonNull(predicate, "predicate is null"); @@ -1985,6 +2683,7 @@ public final Maybe<T> filter(Predicate<? super T> predicate) { * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> flatMap(Function<? super T, ? extends SingleSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2008,6 +2707,7 @@ public final <R> Single<R> flatMap(Function<? super T, ? extends SingleSource<? * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Maybe<R> flatMapMaybe(final Function<? super T, ? extends MaybeSource<? extends R>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2018,7 +2718,7 @@ public final <R> Maybe<R> flatMapMaybe(final Function<? super T, ? extends Maybe * Returns a Flowable that emits items based on applying a specified function to the item emitted by the * source Single, where that function returns a Publisher. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.flatMapObservable.png" alt=""> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.flatMapPublisher.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer @@ -2029,21 +2729,23 @@ public final <R> Maybe<R> flatMapMaybe(final Function<? super T, ? extends Maybe * * @param <R> the result value type * @param mapper - * a function that, when applied to the item emitted by the source Single, returns an + * a function that, when applied to the item emitted by the source Single, returns a * Flowable * @return the Flowable returned from {@code func} when applied to the item emitted by the source Single * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Flowable<R> flatMapPublisher(Function<? super T, ? extends Publisher<? extends R>> mapper) { - return toFlowable().flatMap(mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapPublisher<T, R>(this, mapper)); } /** - * Returns a Flowable that merges each item emitted by the source Single with the values in an - * Iterable corresponding to that item that is generated by a selector. + * Maps the success value of the upstream {@link Single} into an {@link Iterable} and emits its items as a + * {@link Flowable} sequence. * <p> * <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flattenAsFlowable.png" alt=""> * <dl> @@ -2063,6 +2765,7 @@ public final <R> Flowable<R> flatMapPublisher(Function<? super T, ? extends Publ */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? extends Iterable<? extends U>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2070,7 +2773,8 @@ public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? exten } /** - * Returns an Observable that maps a success value into an Iterable and emits its items. + * Maps the success value of the upstream {@link Single} into an {@link Iterable} and emits its items as an + * {@link Observable} sequence. * <p> * <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flattenAsObservable.png" alt=""> * <dl> @@ -2087,6 +2791,7 @@ public final <U> Flowable<U> flattenAsFlowable(final Function<? super T, ? exten * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <U> Observable<U> flattenAsObservable(final Function<? super T, ? extends Iterable<? extends U>> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2110,9 +2815,11 @@ public final <U> Observable<U> flattenAsObservable(final Function<? super T, ? e * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Observable<R> flatMapObservable(Function<? super T, ? extends ObservableSource<? extends R>> mapper) { - return toObservable().flatMap(mapper); + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapObservable<T, R>(this, mapper)); } /** @@ -2133,6 +2840,7 @@ public final <R> Observable<R> flatMapObservable(Function<? super T, ? extends O * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Completable flatMapCompletable(final Function<? super T, ? extends CompletableSource> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); @@ -2142,9 +2850,15 @@ public final Completable flatMapCompletable(final Function<? super T, ? extends /** * Waits in a blocking fashion until the current Single signals a success value (which is returned) or * an exception (which is propagated). + * <p> + * <img width="640" height="429" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.blockingGet.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code blockingGet} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.</dd> * </dl> * @return the success value */ @@ -2157,32 +2871,153 @@ public final T blockingGet() { } /** - * Lifts a function to the current Single and returns a new Single that when subscribed to will pass the - * values of the current Single through the Operator function. + * <strong>This method requires advanced knowledge about building operators, please consider + * other standard composition methods first;</strong> + * Returns a {@code Single} which, when subscribed to, invokes the {@link SingleOperator#apply(SingleObserver) apply(SingleObserver)} method + * of the provided {@link SingleOperator} for each individual downstream {@link Single} and allows the + * insertion of a custom operator by accessing the downstream's {@link SingleObserver} during this subscription phase + * and providing a new {@code SingleObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + * <p> + * <img width="640" height="304" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.lift.png" alt=""> + * <p> + * Generally, such a new {@code SingleObserver} will wrap the downstream's {@code SingleObserver} and forwards the + * {@code onSuccess} and {@code onError} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + * <p> + * Example: + * <pre><code> + * // Step 1: Create the consumer type that will be returned by the SingleOperator.apply(): + * + * public final class CustomSingleObserver<T> implements SingleObserver<T>, Disposable { + * + * // The downstream's SingleObserver that will receive the onXXX events + * final SingleObserver<? super String> downstream; + * + * // The connection to the upstream source that will call this class' onXXX methods + * Disposable upstream; + * + * // The constructor takes the downstream subscriber and usually any other parameters + * public CustomSingleObserver(SingleObserver<? super String> downstream) { + * this.downstream = downstream; + * } + * + * // In the subscription phase, the upstream sends a Disposable to this class + * // and subsequently this class has to send a Disposable to the downstream. + * // Note that relaying the upstream's Disposable directly is not allowed in RxJava + * @Override + * public void onSubscribe(Disposable d) { + * if (upstream != null) { + * d.dispose(); + * } else { + * upstream = d; + * downstream.onSubscribe(this); + * } + * } + * + * // The upstream calls this with the next item and the implementation's + * // responsibility is to emit an item to the downstream based on the intended + * // business logic, or if it can't do so for the particular item, + * // request more from the upstream + * @Override + * public void onSuccess(T item) { + * String str = item.toString(); + * if (str.length() < 2) { + * downstream.onSuccess(str); + * } else { + * // Single is usually expected to produce one of the onXXX events + * downstream.onError(new NoSuchElementException()); + * } + * } + * + * // Some operators may handle the upstream's error while others + * // could just forward it to the downstream. + * @Override + * public void onError(Throwable throwable) { + * downstream.onError(throwable); + * } + * + * // Some operators may use their own resources which should be cleaned up if + * // the downstream disposes the flow before it completed. Operators without + * // resources can simply forward the dispose to the upstream. + * // In some cases, a disposed flag may be set by this method so that other parts + * // of this class may detect the dispose and stop sending events + * // to the downstream. + * @Override + * public void dispose() { + * upstream.dispose(); + * } + * + * // Some operators may simply forward the call to the upstream while others + * // can return the disposed flag set in dispose(). + * @Override + * public boolean isDisposed() { + * return upstream.isDisposed(); + * } + * } + * + * // Step 2: Create a class that implements the SingleOperator interface and + * // returns the custom consumer type from above in its apply() method. + * // Such class may define additional parameters to be submitted to + * // the custom consumer type. + * + * final class CustomSingleOperator<T> implements SingleOperator<String> { + * @Override + * public SingleObserver<? super String> apply(SingleObserver<? super T> upstream) { + * return new CustomSingleObserver<T>(upstream); + * } + * } + * + * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it + * // or reusing an existing one. + * + * Single.just(5) + * .lift(new CustomSingleOperator<Integer>()) + * .test() + * .assertResult("5"); + * + * Single.just(15) + * .lift(new CustomSingleOperator<Integer>()) + * .test() + * .assertFailure(NoSuchElementException.class); + * </code></pre> * <p> - * In other words, this allows chaining TaskExecutors together on a Single for acting on the values within - * the Single. + * Creating custom operators can be complicated and it is recommended one consults the + * <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. * <p> - * {@code task.map(...).filter(...).lift(new OperatorA()).lift(new OperatorB(...)).subscribe() } + * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Single} + * class and creating a {@link SingleTransformer} with it is recommended. * <p> - * If the operator you are creating is designed to act on the item emitted by a source Single, use - * {@code lift}. If your operator is designed to transform the source Single as a whole (for instance, by - * applying a particular set of existing RxJava operators to it) use {@link #compose}. + * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code SingleObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Single}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code SingleObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code SingleTransformer} and extending the {@code Single} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. * <dl> - * <dt><b>Scheduler:</b></dt> - * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link SingleOperator} may use a {@code Scheduler} to support its own asynchronous behavior.</dd> * </dl> * - * @param <R> the downstream's value type (output) - * @param lift - * the Operator that implements the Single-operating function to be applied to the source Single - * @return a Single that is the result of applying the lifted Operator to the source Single - * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators">RxJava wiki: Implementing Your Own Operators</a> + * @param <R> the output value type + * @param lift the {@link SingleOperator} that receives the downstream's {@code SingleObserver} and should return + * a {@code SingleObserver} with custom behavior to be used as the consumer for the current + * {@code Single}. + * @return the new Single instance + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0">RxJava wiki: Writing operators</a> + * @see #compose(SingleTransformer) */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> lift(final SingleOperator<? extends R, ? super T> lift) { - ObjectHelper.requireNonNull(lift, "onLift is null"); + ObjectHelper.requireNonNull(lift, "lift is null"); return RxJavaPlugins.onAssembly(new SingleLift<T, R>(this, lift)); } @@ -2203,15 +3038,38 @@ public final <R> Single<R> lift(final SingleOperator<? extends R, ? super T> lif * @see <a href="http://reactivex.io/documentation/operators/map.html">ReactiveX operators documentation: Map</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <R> Single<R> map(Function<? super T, ? extends R> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaPlugins.onAssembly(new SingleMap<T, R>(this, mapper)); } + /** + * Maps the signal types of this Single into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + * <p> + * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/materialize.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code materialize} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * @return the new Single instance + * @since 2.2.4 - experimental + * @see #dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<Notification<T>> materialize() { + return RxJavaPlugins.onAssembly(new SingleMaterialize<T>(this)); + } + /** * Signals true if the current Single signals a success value that is Object-equals with the value * provided. + * <p> + * <img width="640" height="401" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.contains.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code contains} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2229,6 +3087,8 @@ public final Single<Boolean> contains(Object value) { /** * Signals true if the current Single signals a success value that is equal with * the value provided by calling a bi-predicate. + * <p> + * <img width="640" height="401" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.contains.f.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code contains} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2240,6 +3100,7 @@ public final Single<Boolean> contains(Object value) { * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<Boolean> contains(final Object value, final BiPredicate<Object, Object> comparer) { ObjectHelper.requireNonNull(value, "value is null"); @@ -2250,7 +3111,7 @@ public final Single<Boolean> contains(final Object value, final BiPredicate<Obje /** * Flattens this and another Single into a single Flowable, without any transformation. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.merge.png" alt=""> + * <img width="640" height="415" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.mergeWith.png" alt=""> * <p> * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using * the {@code mergeWith} method. @@ -2262,7 +3123,7 @@ public final Single<Boolean> contains(final Object value, final BiPredicate<Obje * </dl> * * @param other - * a Single to be merged + * a SingleSource to be merged * @return that emits all of the items emitted by the source Singles * @see <a href="http://reactivex.io/documentation/operators/merge.html">ReactiveX operators documentation: Merge</a> */ @@ -2280,18 +3141,20 @@ public final Flowable<T> mergeWith(SingleSource<? extends T> other) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.observeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>you specify which {@link Scheduler} this operator will use</dd> + * <dd>you specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler * the {@link Scheduler} to notify subscribers on * @return the source Single modified so that its subscribers are notified on the specified * {@link Scheduler} + * @throws NullPointerException if scheduler is null * @see <a href="http://reactivex.io/documentation/operators/observeon.html">ReactiveX operators documentation: ObserveOn</a> * @see <a href="http://www.grahamlea.com/2014/07/rxjava-threading-examples/">RxJava Threading Examples</a> * @see #subscribeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single<T> observeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -2302,7 +3165,7 @@ public final Single<T> observeOn(final Scheduler scheduler) { * Instructs a Single to emit an item (returned by a specified function) rather than invoking * {@link SingleObserver#onError onError} if it encounters an error. * <p> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onErrorReturn.png" alt=""> + * <img width="640" height="451" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onErrorReturn.png" alt=""> * <p> * By default, when a Single encounters an error that prevents it from emitting the expected item to its * subscriber, the Single invokes its subscriber's {@link SingleObserver#onError} method, and then quits @@ -2325,6 +3188,7 @@ public final Single<T> observeOn(final Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> onErrorReturn(final Function<Throwable, ? extends T> resumeFunction) { ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); @@ -2333,6 +3197,8 @@ public final Single<T> onErrorReturn(final Function<Throwable, ? extends T> resu /** * Signals the specified value as success in case the current Single signals an error. + * <p> + * <img width="640" height="451" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onErrorReturnItem.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2342,6 +3208,7 @@ public final Single<T> onErrorReturn(final Function<Throwable, ? extends T> resu * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> onErrorReturnItem(final T value) { ObjectHelper.requireNonNull(value, "value is null"); @@ -2351,9 +3218,9 @@ public final Single<T> onErrorReturnItem(final T value) { /** * Instructs a Single to pass control to another Single rather than invoking * {@link SingleObserver#onError(Throwable)} if it encounters an error. - * <p/> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorResumeNext.png" alt=""> - * <p/> + * <p> + * <img width="640" height="451" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onErrorResumeNext.png" alt=""> + * <p> * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this @@ -2363,7 +3230,7 @@ public final Single<T> onErrorReturnItem(final T value) { * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error * happened. - * <p/> + * <p> * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. * <dl> @@ -2376,6 +3243,7 @@ public final Single<T> onErrorReturnItem(final T value) { * @see <a href="http://reactivex.io/documentation/operators/catch.html">ReactiveX operators documentation: Catch</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> onErrorResumeNext(final Single<? extends T> resumeSingleInCaseOfError) { ObjectHelper.requireNonNull(resumeSingleInCaseOfError, "resumeSingleInCaseOfError is null"); @@ -2385,9 +3253,9 @@ public final Single<T> onErrorResumeNext(final Single<? extends T> resumeSingleI /** * Instructs a Single to pass control to another Single rather than invoking * {@link SingleObserver#onError(Throwable)} if it encounters an error. - * <p/> - * <img width="640" height="310" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorResumeNext.png" alt=""> - * <p/> + * <p> + * <img width="640" height="451" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onErrorResumeNext.f.png" alt=""> + * <p> * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this @@ -2397,7 +3265,7 @@ public final Single<T> onErrorResumeNext(final Single<? extends T> resumeSingleI * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error * happened. - * <p/> + * <p> * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. * <dl> @@ -2411,6 +3279,7 @@ public final Single<T> onErrorResumeNext(final Single<? extends T> resumeSingleI * @since .20 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> onErrorResumeNext( final Function<? super Throwable, ? extends SingleSource<? extends T>> resumeFunctionInCaseOfError) { @@ -2418,8 +3287,30 @@ public final Single<T> onErrorResumeNext( return RxJavaPlugins.onAssembly(new SingleResumeNext<T>(this, resumeFunctionInCaseOfError)); } + /** + * Nulls out references to the upstream producer and downstream SingleObserver if + * the sequence is terminated or downstream calls dispose(). + * <p> + * <img width="640" height="346" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.onTerminateDetach.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.5 - experimental + * @return a Single which nulls out references to the upstream producer and downstream SingleObserver if + * the sequence is terminated or downstream calls dispose() + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<T> onTerminateDetach() { + return RxJavaPlugins.onAssembly(new SingleDetach<T>(this)); + } + /** * Repeatedly re-subscribes to the current Single and emits each success value. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.repeat.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -2438,6 +3329,8 @@ public final Flowable<T> repeat() { /** * Re-subscribes to the current Single at most the given number of times and emits each success value. + * <p> + * <img width="640" height="457" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.repeat.n.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -2459,6 +3352,8 @@ public final Flowable<T> repeat(long times) { * Re-subscribes to the current Single if * the Publisher returned by the handler function signals a value in response to a * value signalled through the Flowable the handle receives. + * <p> + * <img width="640" height="1478" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.repeatWhen.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer. @@ -2482,6 +3377,8 @@ public final Flowable<T> repeatWhen(Function<? super Flowable<Object>, ? extends /** * Re-subscribes to the current Single until the given BooleanSupplier returns true. + * <p> + * <img width="640" height="463" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.repeatUntil.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -2502,6 +3399,8 @@ public final Flowable<T> repeatUntil(BooleanSupplier stop) { /** * Repeatedly re-subscribes to the current Single indefinitely if it fails with an onError. + * <p> + * <img width="640" height="399" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retry.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2518,6 +3417,8 @@ public final Single<T> retry() { /** * Repeatedly re-subscribe at most the specified times to the current Single * if it fails with an onError. + * <p> + * <img width="640" height="329" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retry.n.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2535,6 +3436,8 @@ public final Single<T> retry(long times) { /** * Re-subscribe to the current Single if the given predicate returns true when the Single fails * with an onError. + * <p> + * <img width="640" height="230" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retry.f2.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2550,9 +3453,33 @@ public final Single<T> retry(BiPredicate<? super Integer, ? super Throwable> pre return toSingle(toFlowable().retry(predicate)); } + /** + * Repeatedly re-subscribe at most times or until the predicate returns false, whichever happens first + * if it fails with an onError. + * <p> + * <img width="640" height="259" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retry.nf.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.8 - experimental + * @param times the number of times to resubscribe if the current Single fails + * @param predicate the predicate called with the failure Throwable + * and should return true if a resubscription should happen + * @return the new Single instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single<T> retry(long times, Predicate<? super Throwable> predicate) { + return toSingle(toFlowable().retry(times, predicate)); + } + /** * Re-subscribe to the current Single if the given predicate returns true when the Single fails * with an onError. + * <p> + * <img width="640" height="240" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retry.f.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retry} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2572,7 +3499,34 @@ public final Single<T> retry(Predicate<? super Throwable> predicate) { * Re-subscribes to the current Single if and when the Publisher returned by the handler * function signals a value. * <p> + * <img width="640" height="405" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.retryWhen.png" alt=""> + * <p> * If the Publisher signals an onComplete, the resulting Single will signal a NoSuchElementException. + * <p> + * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + * <p> + * The following example demonstrates how to retry an asynchronous source with a delay: + * <pre><code> + * Single.timer(1, TimeUnit.SECONDS) + * .doOnSubscribe(s -> System.out.println("subscribing")) + * .map(v -> { throw new RuntimeException(); }) + * .retryWhen(errors -> { + * AtomicInteger counter = new AtomicInteger(); + * return errors + * .takeWhile(e -> counter.getAndIncrement() != 3) + * .flatMap(e -> { + * System.out.println("delay retry by " + counter.get() + " second(s)"); + * return Flowable.timer(counter.get(), TimeUnit.SECONDS); + * }); + * }) + * .blockingGet(); + * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code retryWhen} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2593,6 +3547,8 @@ public final Single<T> retryWhen(Function<? super Flowable<Throwable>, ? extends /** * Subscribes to a Single but ignore its emission or notification. * <p> + * <img width="640" height="340" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribe.png" alt=""> + * <p> * If the Single emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -2612,6 +3568,8 @@ public final Disposable subscribe() { /** * Subscribes to a Single and provides a composite callback to handle the item it emits * or any error notification it issues. + * <p> + * <img width="640" height="340" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribe.c2.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2626,18 +3584,21 @@ public final Disposable subscribe() { * if {@code onCallback} is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final BiConsumer<? super T, ? super Throwable> onCallback) { ObjectHelper.requireNonNull(onCallback, "onCallback is null"); - BiConsumerSingleObserver<T> s = new BiConsumerSingleObserver<T>(onCallback); - subscribe(s); - return s; + BiConsumerSingleObserver<T> observer = new BiConsumerSingleObserver<T>(onCallback); + subscribe(observer); + return observer; } /** * Subscribes to a Single and provides a callback to handle the item it emits. * <p> + * <img width="640" height="341" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribe.c.png" alt=""> + * <p> * If the Single emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -2662,6 +3623,8 @@ public final Disposable subscribe(Consumer<? super T> onSuccess) { /** * Subscribes to a Single and provides callbacks to handle the item it emits or any error notification it * issues. + * <p> + * <img width="640" height="340" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribe.cc.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code subscribe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2679,27 +3642,28 @@ public final Disposable subscribe(Consumer<? super T> onSuccess) { * if {@code onError} is null */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Disposable subscribe(final Consumer<? super T> onSuccess, final Consumer<? super Throwable> onError) { ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); ObjectHelper.requireNonNull(onError, "onError is null"); - ConsumerSingleObserver<T> s = new ConsumerSingleObserver<T>(onSuccess, onError); - subscribe(s); - return s; + ConsumerSingleObserver<T> observer = new ConsumerSingleObserver<T>(onSuccess, onError); + subscribe(observer); + return observer; } @SchedulerSupport(SchedulerSupport.NONE) @Override - public final void subscribe(SingleObserver<? super T> subscriber) { - ObjectHelper.requireNonNull(subscriber, "subscriber is null"); + public final void subscribe(SingleObserver<? super T> observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); - subscriber = RxJavaPlugins.onSubscribe(this, subscriber); + observer = RxJavaPlugins.onSubscribe(this, observer); - ObjectHelper.requireNonNull(subscriber, "subscriber returned by the RxJavaPlugins hook is null"); + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null SingleObserver. Please check the handler provided to RxJavaPlugins.setOnSingleSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); try { - subscribeActual(subscriber); + subscribeActual(observer); } catch (NullPointerException ex) { throw ex; } catch (Throwable ex) { @@ -2711,7 +3675,10 @@ public final void subscribe(SingleObserver<? super T> subscriber) { } /** - * Override this method in subclasses to handle the incoming SingleObservers. + * Implement this method in subclasses to handle the incoming {@link SingleObserver}s. + * <p>There is no need to call any of the plugin hooks on the current {@code Single} instance or + * the {@code SingleObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(SingleObserver)} before this method gets called. * @param observer the SingleObserver to handle, not null */ protected abstract void subscribeActual(@NonNull SingleObserver<? super T> observer); @@ -2719,16 +3686,18 @@ public final void subscribe(SingleObserver<? super T> subscriber) { /** * Subscribes a given SingleObserver (subclass) to this Single and returns the given * SingleObserver as is. + * <p> + * <img width="640" height="338" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribeWith.png" alt=""> * <p>Usage example: * <pre><code> * Single<Integer> source = Single.just(1); * CompositeDisposable composite = new CompositeDisposable(); * - * class ResourceSingleObserver implements SingleObserver<Integer>, Disposable { + * DisposableSingleObserver<Integer> ds = new DisposableSingleObserver<>() { * // ... - * } + * }; * - * composite.add(source.subscribeWith(new ResourceSingleObserver())); + * composite.add(source.subscribeWith(ds)); * </code></pre> * <dl> * <dt><b>Scheduler:</b></dt> @@ -2753,7 +3722,7 @@ public final <E extends SingleObserver<? super T>> E subscribeWith(E observer) { * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.subscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> - * <dd>You specify which {@link Scheduler} this operator will use</dd> + * <dd>You specify which {@link Scheduler} this operator will use.</dd> * </dl> * * @param scheduler @@ -2764,6 +3733,7 @@ public final <E extends SingleObserver<? super T>> E subscribeWith(E observer) { * @see #observeOn */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single<T> subscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -2775,7 +3745,7 @@ public final Single<T> subscribeOn(final Scheduler scheduler) { * termination of {@code other}, this will emit a {@link CancellationException} rather than go to * {@link SingleObserver#onSuccess(Object)}. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeUntil.png" alt=""> + * <img width="640" height="333" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.takeUntil.c.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code takeUntil} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2788,6 +3758,7 @@ public final Single<T> subscribeOn(final Scheduler scheduler) { * @see <a href="http://reactivex.io/documentation/operators/takeuntil.html">ReactiveX operators documentation: TakeUntil</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single<T> takeUntil(final CompletableSource other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2799,7 +3770,7 @@ public final Single<T> takeUntil(final CompletableSource other) { * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to * {@link SingleObserver#onSuccess(Object)}. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeUntil.png" alt=""> + * <img width="640" height="215" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.takeUntil.p.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The {@code other} publisher is consumed in an unbounded fashion but will be @@ -2819,6 +3790,7 @@ public final Single<T> takeUntil(final CompletableSource other) { */ @BackpressureSupport(BackpressureKind.FULL) @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <E> Single<T> takeUntil(final Publisher<E> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2830,7 +3802,7 @@ public final <E> Single<T> takeUntil(final Publisher<E> other) { * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to * {@link SingleObserver#onSuccess(Object)}. * <p> - * <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeUntil.png" alt=""> + * <img width="640" height="314" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.takeUntil.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code takeUntil} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2844,6 +3816,7 @@ public final <E> Single<T> takeUntil(final Publisher<E> other) { * @see <a href="http://reactivex.io/documentation/operators/takeuntil.html">ReactiveX operators documentation: TakeUntil</a> */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final <E> Single<T> takeUntil(final SingleSource<? extends E> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2853,6 +3826,8 @@ public final <E> Single<T> takeUntil(final SingleSource<? extends E> other) { /** * Signals a TimeoutException if the current Single doesn't signal a success value within the * specified timeout window. + * <p> + * <img width="640" height="364" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timeout.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} signals the TimeoutException on the {@code computation} {@link Scheduler}.</dd> @@ -2871,6 +3846,8 @@ public final Single<T> timeout(long timeout, TimeUnit unit) { /** * Signals a TimeoutException if the current Single doesn't signal a success value within the * specified timeout window. + * <p> + * <img width="640" height="334" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timeout.s.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} signals the TimeoutException on the {@link Scheduler} you specify.</dd> @@ -2890,7 +3867,9 @@ public final Single<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler) /** * Runs the current Single and if it doesn't signal within the specified timeout window, it is - * cancelled and the other SingleSource subscribed to. + * disposed and the other SingleSource subscribed to. + * <p> + * <img width="640" height="283" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timeout.sb.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} subscribes to the other SingleSource on the {@link Scheduler} you specify.</dd> @@ -2903,6 +3882,7 @@ public final Single<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler) * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) public final Single<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler, SingleSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2911,7 +3891,9 @@ public final Single<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler, /** * Runs the current Single and if it doesn't signal within the specified timeout window, it is - * cancelled and the other SingleSource subscribed to. + * disposed and the other SingleSource subscribed to. + * <p> + * <img width="640" height="282" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.timeout.b.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code timeout} subscribes to the other SingleSource on @@ -2921,9 +3903,14 @@ public final Single<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler, * @param unit the time unit * @param other the other SingleSource that gets subscribed to if the current Single times out * @return the new Single instance + * @throws NullPointerException + * if other is null, or + * if unit is null, or + * if scheduler is null * @since 2.0 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Single<T> timeout(long timeout, TimeUnit unit, SingleSource<? extends T> other) { ObjectHelper.requireNonNull(other, "other is null"); @@ -2939,6 +3926,8 @@ private Single<T> timeout0(final long timeout, final TimeUnit unit, final Schedu /** * Calls the specified converter function with the current Single instance * during assembly time and returns its result. + * <p> + * <img width="640" height="553" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.to.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code to} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2965,9 +3954,7 @@ public final <R> R to(Function<? super Single<T>, R> convert) { * and calls {@code onComplete} when this source {@link Single} calls * {@code onSuccess}. Error terminal event is propagated. * <p> - * <img width="640" height="295" src= - * "https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toCompletable.png" - * alt=""> + * <img width="640" height="436" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toCompletable.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toCompletable} does not operate by default on a particular {@link Scheduler}.</dd> @@ -2975,19 +3962,40 @@ public final <R> R to(Function<? super Single<T>, R> convert) { * * @return a {@link Completable} that calls {@code onComplete} on it's subscriber when the source {@link Single} * calls {@code onSuccess}. - * @see <a href="http://reactivex.io/documentation/completable.html">ReactiveX documentation: Completable</a> * @since 2.0 + * @deprecated see {@link #ignoreElement()} instead, will be removed in 3.0 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) + @Deprecated public final Completable toCompletable() { return RxJavaPlugins.onAssembly(new CompletableFromSingle<T>(this)); } + /** + * Returns a {@link Completable} that ignores the success value of this {@link Single} + * and calls {@code onComplete} instead on the returned {@code Completable}. + * <p> + * <img width="640" height="436" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.ignoreElement.png" alt=""> + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code ignoreElement} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @return a {@link Completable} that calls {@code onComplete} on it's observer when the source {@link Single} + * calls {@code onSuccess}. + * @since 2.1.13 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ignoreElement() { + return RxJavaPlugins.onAssembly(new CompletableFromSingle<T>(this)); + } + /** * Converts this Single into a {@link Flowable}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toObservable.png" alt=""> + * <img width="640" height="462" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toFlowable.png" alt=""> * <dl> * <dt><b>Backpressure:</b></dt> * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer.</dd> @@ -3011,7 +4019,7 @@ public final Flowable<T> toFlowable() { /** * Returns a {@link Future} representing the single value emitted by this {@code Single}. * <p> - * <img width="640" height="395" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toFuture.png" alt=""> + * <img width="640" height="467" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/Single.toFuture.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toFuture} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3029,7 +4037,7 @@ public final Future<T> toFuture() { /** * Converts this Single into a {@link Maybe}. * <p> - * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toObservable.png" alt=""> + * <img width="640" height="463" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toMaybe.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code toMaybe} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3069,19 +4077,22 @@ public final Observable<T> toObservable() { /** * Returns a Single which makes sure when a SingleObserver disposes the Disposable, - * that call is propagated up on the specified scheduler + * that call is propagated up on the specified scheduler. + * <p> + * <img width="640" height="693" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.unsubscribeOn.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.</dd> * </dl> - * @param scheduler the target scheduler where to execute the cancellation + * <p>History: 2.0.9 - experimental + * @param scheduler the target scheduler where to execute the disposal * @return the new Single instance * @throws NullPointerException if scheduler is null - * @since 2.0.9 - experimental + * @since 2.2 */ @CheckReturnValue + @NonNull @SchedulerSupport(SchedulerSupport.CUSTOM) - @Experimental public final Single<T> unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); return RxJavaPlugins.onAssembly(new SingleUnsubscribeOn<T>(this, scheduler)); @@ -3122,6 +4133,8 @@ public final <U, R> Single<R> zipWith(SingleSource<U> other, BiFunction<? super /** * Creates a TestObserver and subscribes * it to this Single. + * <p> + * <img width="640" height="442" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.test.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code test} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3132,13 +4145,15 @@ public final <U, R> Single<R> zipWith(SingleSource<U> other, BiFunction<? super @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test() { - TestObserver<T> ts = new TestObserver<T>(); - subscribe(ts); - return ts; + TestObserver<T> to = new TestObserver<T>(); + subscribe(to); + return to; } /** * Creates a TestObserver optionally in cancelled state, then subscribes it to this Single. + * <p> + * <img width="640" height="482" src="https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.test.b.png" alt=""> * <dl> * <dt><b>Scheduler:</b></dt> * <dd>{@code test} does not operate by default on a particular {@link Scheduler}.</dd> @@ -3151,14 +4166,14 @@ public final TestObserver<T> test() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final TestObserver<T> test(boolean cancelled) { - TestObserver<T> ts = new TestObserver<T>(); + TestObserver<T> to = new TestObserver<T>(); if (cancelled) { - ts.cancel(); + to.cancel(); } - subscribe(ts); - return ts; + subscribe(to); + return to; } private static <T> Single<T> toSingle(Flowable<T> source) { diff --git a/src/main/java/io/reactivex/SingleConverter.java b/src/main/java/io/reactivex/SingleConverter.java new file mode 100644 index 0000000000..1e3944f73b --- /dev/null +++ b/src/main/java/io/reactivex/SingleConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Single#as} operator to turn a Single into another + * value fluently. + * <p>History: 2.1.7 - experimental + * @param <T> the upstream type + * @param <R> the output type + * @since 2.2 + */ +public interface SingleConverter<T, R> { + /** + * Applies a function to the upstream Single and returns a converted value of type {@code R}. + * + * @param upstream the upstream Single instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Single<T> upstream); +} diff --git a/src/main/java/io/reactivex/SingleEmitter.java b/src/main/java/io/reactivex/SingleEmitter.java index 6c0da0ce06..9c1ded1575 100644 --- a/src/main/java/io/reactivex/SingleEmitter.java +++ b/src/main/java/io/reactivex/SingleEmitter.java @@ -21,9 +21,29 @@ * Abstraction over an RxJava {@link SingleObserver} that allows associating * a resource with it. * <p> - * All methods are safe to call from multiple threads. + * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. * <p> - * Calling onSuccess or onError multiple times has no effect. + * Calling {@link #onSuccess(Object)} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after {@code onSuccess} will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * <p> + * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onSuccess(Object)}, + * {@link #onError(Throwable)}, or when {@link #tryOnError(Throwable)} succeeds. + * <p> + * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + * <p> + * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. * * @param <T> the value type to emit */ @@ -43,21 +63,24 @@ public interface SingleEmitter<T> { /** * Sets a Disposable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. - * @param s the disposable, null is allowed + * or Cancellable will be disposed/cancelled. + * @param d the disposable, null is allowed */ - void setDisposable(@Nullable Disposable s); + void setDisposable(@Nullable Disposable d); /** - * Sets a Cancellable on this emitter; any previous Disposable - * or Cancellation will be unsubscribed/cancelled. + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. * @param c the cancellable resource, null is allowed */ void setCancellable(@Nullable Cancellable c); /** - * Returns true if the downstream cancelled the sequence. - * @return true if the downstream cancelled the sequence + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onSuccess(Object)}, {@link #onError(Throwable)}, + * or a successful {@link #tryOnError(Throwable)}. + * <p>This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated */ boolean isDisposed(); @@ -68,11 +91,11 @@ public interface SingleEmitter<T> { * <p> * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called * if the error could not be delivered. + * <p>History: 2.1.1 - experimental * @param t the throwable error to signal if possible * @return true if successful, false if the downstream is not able to accept further * events - * @since 2.1.1 - experimental + * @since 2.2 */ - @Experimental boolean tryOnError(@NonNull Throwable t); } diff --git a/src/main/java/io/reactivex/SingleObserver.java b/src/main/java/io/reactivex/SingleObserver.java index eeda4af342..6fefe167a8 100644 --- a/src/main/java/io/reactivex/SingleObserver.java +++ b/src/main/java/io/reactivex/SingleObserver.java @@ -17,14 +17,34 @@ import io.reactivex.disposables.Disposable; /** - * Provides a mechanism for receiving push-based notifications. + * Provides a mechanism for receiving push-based notification of a single value or an error. * <p> - * After a SingleObserver calls a {@link Single}'s {@link Single#subscribe subscribe} method, - * first the Single calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows - * cancelling the sequence at any time, then the - * {@code Single} calls only one of the SingleObserver {@link #onSuccess} and {@link #onError} methods to provide - * notifications. - * + * When a {@code SingleObserver} is subscribed to a {@link SingleSource} through the {@link SingleSource#subscribe(SingleObserver)} method, + * the {@code SingleSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code SingleSource} will call a {@code SingleObserver}'s {@link #onSuccess(Object)} method exactly once or the {@code SingleObserver}'s + * {@link #onError} method exactly once as they are considered mutually exclusive <strong>terminal signals</strong>. + * <p> + * Calling the {@code SingleObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + * <pre><code> onSubscribe (onSuccess | onError)?</code></pre> + * <p> + * Subscribing a {@code SingleObserver} to multiple {@code SingleSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code SingleObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + * <p> + * Calling {@link #onSubscribe(Disposable)}, {@link #onSuccess(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + * <p> + * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + * <ul> + * <li>If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + * </li> + * <li>If there is a fatal error (such as {@code VirtualMachineError}).</li> + * </ul> * @see <a href="http://reactivex.io/documentation/observable.html">ReactiveX documentation: Observable</a> * @param <T> * the type of item the SingleObserver expects to observe diff --git a/src/main/java/io/reactivex/SingleOnSubscribe.java b/src/main/java/io/reactivex/SingleOnSubscribe.java index 1f65502729..aa12a0dcd4 100644 --- a/src/main/java/io/reactivex/SingleOnSubscribe.java +++ b/src/main/java/io/reactivex/SingleOnSubscribe.java @@ -25,9 +25,9 @@ public interface SingleOnSubscribe<T> { /** * Called for each SingleObserver that subscribes. - * @param e the safe emitter instance, never null + * @param emitter the safe emitter instance, never null * @throws Exception on error */ - void subscribe(@NonNull SingleEmitter<T> e) throws Exception; + void subscribe(@NonNull SingleEmitter<T> emitter) throws Exception; } diff --git a/src/main/java/io/reactivex/annotations/SchedulerSupport.java b/src/main/java/io/reactivex/annotations/SchedulerSupport.java index f2c7be4d68..09acaa6dea 100644 --- a/src/main/java/io/reactivex/annotations/SchedulerSupport.java +++ b/src/main/java/io/reactivex/annotations/SchedulerSupport.java @@ -28,7 +28,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Documented -@Target({ElementType.METHOD, ElementType.TYPE}) +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) public @interface SchedulerSupport { /** * A special value indicating the operator/class doesn't use schedulers. @@ -63,9 +63,9 @@ /** * The operator/class runs on RxJava's {@linkplain Schedulers#single() single scheduler} * or takes timing information from it. - * @since 2.0.8 - experimental + * <p>History: 2.0.8 - experimental + * @since 2.2 */ - @Experimental String SINGLE = "io.reactivex:single"; /** diff --git a/src/main/java/io/reactivex/disposables/ActionDisposable.java b/src/main/java/io/reactivex/disposables/ActionDisposable.java index 447dfe2e34..f553f8b58e 100644 --- a/src/main/java/io/reactivex/disposables/ActionDisposable.java +++ b/src/main/java/io/reactivex/disposables/ActionDisposable.java @@ -16,6 +16,9 @@ import io.reactivex.functions.Action; import io.reactivex.internal.util.ExceptionHelper; +/** + * A Disposable container that manages an Action instance. + */ final class ActionDisposable extends ReferenceDisposable<Action> { private static final long serialVersionUID = -8219729196779211169L; diff --git a/src/main/java/io/reactivex/disposables/CompositeDisposable.java b/src/main/java/io/reactivex/disposables/CompositeDisposable.java index f9bf2d9e84..f7a1bf4a36 100644 --- a/src/main/java/io/reactivex/disposables/CompositeDisposable.java +++ b/src/main/java/io/reactivex/disposables/CompositeDisposable.java @@ -38,26 +38,28 @@ public CompositeDisposable() { /** * Creates a CompositeDisposables with the given array of initial elements. - * @param resources the array of Disposables to start with + * @param disposables the array of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its array items is null */ - public CompositeDisposable(@NonNull Disposable... resources) { - ObjectHelper.requireNonNull(resources, "resources is null"); - this.resources = new OpenHashSet<Disposable>(resources.length + 1); - for (Disposable d : resources) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + public CompositeDisposable(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); + this.resources = new OpenHashSet<Disposable>(disposables.length + 1); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); this.resources.add(d); } } /** * Creates a CompositeDisposables with the given Iterable sequence of initial elements. - * @param resources the Iterable sequence of Disposables to start with + * @param disposables the Iterable sequence of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its items is null */ - public CompositeDisposable(@NonNull Iterable<? extends Disposable> resources) { - ObjectHelper.requireNonNull(resources, "resources is null"); + public CompositeDisposable(@NonNull Iterable<? extends Disposable> disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); this.resources = new OpenHashSet<Disposable>(); - for (Disposable d : resources) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable item in the disposables sequence is null"); this.resources.add(d); } } @@ -85,9 +87,16 @@ public boolean isDisposed() { return disposed; } + /** + * Adds a disposable to this container or disposes it if the + * container has been disposed. + * @param disposable the disposable to add, not null + * @return true if successful, false if this container has been disposed + * @throws NullPointerException if {@code disposable} is null + */ @Override - public boolean add(@NonNull Disposable d) { - ObjectHelper.requireNonNull(d, "d is null"); + public boolean add(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposable is null"); if (!disposed) { synchronized (this) { if (!disposed) { @@ -96,57 +105,71 @@ public boolean add(@NonNull Disposable d) { set = new OpenHashSet<Disposable>(); resources = set; } - set.add(d); + set.add(disposable); return true; } } } - d.dispose(); + disposable.dispose(); return false; } /** * Atomically adds the given array of Disposables to the container or * disposes them all if the container has been disposed. - * @param ds the array of Disposables + * @param disposables the array of Disposables * @return true if the operation was successful, false if the container has been disposed + * @throws NullPointerException if {@code disposables} or any of its array items is null */ - public boolean addAll(@NonNull Disposable... ds) { - ObjectHelper.requireNonNull(ds, "ds is null"); + public boolean addAll(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); if (!disposed) { synchronized (this) { if (!disposed) { OpenHashSet<Disposable> set = resources; if (set == null) { - set = new OpenHashSet<Disposable>(ds.length + 1); + set = new OpenHashSet<Disposable>(disposables.length + 1); resources = set; } - for (Disposable d : ds) { - ObjectHelper.requireNonNull(d, "d is null"); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); set.add(d); } return true; } } } - for (Disposable d : ds) { + for (Disposable d : disposables) { d.dispose(); } return false; } + /** + * Removes and disposes the given disposable if it is part of this + * container. + * @param disposable the disposable to remove and dispose, not null + * @return true if the operation was successful + */ @Override - public boolean remove(@NonNull Disposable d) { - if (delete(d)) { - d.dispose(); + public boolean remove(@NonNull Disposable disposable) { + if (delete(disposable)) { + disposable.dispose(); return true; } return false; } + /** + * Removes (but does not dispose) the given disposable if it is part of this + * container. + * @param disposable the disposable to remove, not null + * @return true if the operation was successful + * @throws NullPointerException if {@code disposable} is null + */ @Override - public boolean delete(@NonNull Disposable d) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + public boolean delete(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposables is null"); if (disposed) { return false; } @@ -156,7 +179,7 @@ public boolean delete(@NonNull Disposable d) { } OpenHashSet<Disposable> set = resources; - if (set == null || !set.remove(d)) { + if (set == null || !set.remove(disposable)) { return false; } } diff --git a/src/main/java/io/reactivex/exceptions/CompositeException.java b/src/main/java/io/reactivex/exceptions/CompositeException.java index 0b18f8ce3f..4915688379 100644 --- a/src/main/java/io/reactivex/exceptions/CompositeException.java +++ b/src/main/java/io/reactivex/exceptions/CompositeException.java @@ -278,9 +278,9 @@ public int size() { * @param e the {@link Throwable} {@code e}. * @return The root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself. */ - private Throwable getRootCause(Throwable e) { + /*private */Throwable getRootCause(Throwable e) { Throwable root = e.getCause(); - if (root == null || cause == root) { + if (root == null || e == root) { return e; } while (true) { diff --git a/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java index e0e4d8e336..1cfe421916 100644 --- a/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java +++ b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java @@ -19,10 +19,9 @@ * Represents an exception used to signal to the {@code RxJavaPlugins.onError()} that a * callback-based subscribe() method on a base reactive type didn't specify * an onError handler. - * <p>History: 2.0.6 - experimental - * @since 2.1 - beta + * <p>History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 */ -@Beta public final class OnErrorNotImplementedException extends RuntimeException { private static final long serialVersionUID = -6298857009889503852L; @@ -49,6 +48,6 @@ public OnErrorNotImplementedException(String message, @NonNull Throwable e) { * the {@code Throwable} to signal; if null, a NullPointerException is constructed */ public OnErrorNotImplementedException(@NonNull Throwable e) { - super(e != null ? e.getMessage() : null, e != null ? e : new NullPointerException()); + this("The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | " + e, e); } } \ No newline at end of file diff --git a/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java index 00a3ee3bbc..ff36ce1cf3 100644 --- a/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java +++ b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java @@ -13,15 +13,12 @@ package io.reactivex.exceptions; -import io.reactivex.annotations.Beta; - /** - * Explicitly named exception to indicate a Reactive-Streams + * Explicitly named exception to indicate a Reactive Streams * protocol violation. - * <p>History: 2.0.6 - experimental - * @since 2.1 - beta + * <p>History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 */ -@Beta public final class ProtocolViolationException extends IllegalStateException { private static final long serialVersionUID = 1644750035281290266L; diff --git a/src/main/java/io/reactivex/exceptions/UndeliverableException.java b/src/main/java/io/reactivex/exceptions/UndeliverableException.java index 030ba755ba..d3923e0cdd 100644 --- a/src/main/java/io/reactivex/exceptions/UndeliverableException.java +++ b/src/main/java/io/reactivex/exceptions/UndeliverableException.java @@ -13,14 +13,11 @@ package io.reactivex.exceptions; -import io.reactivex.annotations.Beta; - /** * Wrapper for Throwable errors that are sent to `RxJavaPlugins.onError`. - * <p>History: 2.0.6 - experimental - * @since 2.1 - beta + * <p>History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 */ -@Beta public final class UndeliverableException extends IllegalStateException { private static final long serialVersionUID = 1644750035281290266L; @@ -31,6 +28,6 @@ public final class UndeliverableException extends IllegalStateException { * @param cause the cause, not null */ public UndeliverableException(Throwable cause) { - super(cause); + super("The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | " + cause, cause); } } diff --git a/src/main/java/io/reactivex/flowables/ConnectableFlowable.java b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java index 51dad2c11d..ef5b667bac 100644 --- a/src/main/java/io/reactivex/flowables/ConnectableFlowable.java +++ b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java @@ -13,34 +13,37 @@ package io.reactivex.flowables; -import io.reactivex.annotations.NonNull; +import java.util.concurrent.TimeUnit; + import org.reactivestreams.Subscriber; -import io.reactivex.Flowable; +import io.reactivex.*; +import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.functions.*; import io.reactivex.internal.operators.flowable.*; import io.reactivex.internal.util.ConnectConsumer; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; /** - * A {@code ConnectableObservable} resembles an ordinary {@link Flowable}, except that it does not begin + * A {@code ConnectableFlowable} resembles an ordinary {@link Flowable}, except that it does not begin * emitting items when it is subscribed to, but only when its {@link #connect} method is called. In this way you - * can wait for all intended {@link Subscriber}s to {@link Flowable#subscribe} to the {@code Observable} - * before the {@code Observable} begins emitting items. + * can wait for all intended {@link Subscriber}s to {@link Flowable#subscribe} to the {@code Flowable} + * before the {@code Flowable} begins emitting items. * <p> * <img width="640" height="510" src="https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/publishConnect.png" alt=""> * * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators">RxJava Wiki: * Connectable Observable Operators</a> * @param <T> - * the type of items emitted by the {@code ConnectableObservable} + * the type of items emitted by the {@code ConnectableFlowable} */ public abstract class ConnectableFlowable<T> extends Flowable<T> { /** - * Instructs the {@code ConnectableObservable} to begin emitting the items from its underlying + * Instructs the {@code ConnectableFlowable} to begin emitting the items from its underlying * {@link Flowable} to its {@link Subscriber}s. * * @param connection @@ -51,7 +54,7 @@ public abstract class ConnectableFlowable<T> extends Flowable<T> { public abstract void connect(@NonNull Consumer<? super Disposable> connection); /** - * Instructs the {@code ConnectableObservable} to begin emitting the items from its underlying + * Instructs the {@code ConnectableFlowable} to begin emitting the items from its underlying * {@link Flowable} to its {@link Subscriber}s. * <p> * To disconnect from a synchronous source, use the {@link #connect(io.reactivex.functions.Consumer)} method. @@ -66,36 +69,219 @@ public final Disposable connect() { } /** - * Returns an {@code Observable} that stays connected to this {@code ConnectableObservable} as long as there - * is at least one subscription to this {@code ConnectableObservable}. - * + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing subscribers and refCount won't hang. + * + * @return the ConnectableFlowable to work with + * @since 2.2.10 + */ + private ConnectableFlowable<T> onRefCount() { + if (this instanceof FlowablePublishClassic) { + @SuppressWarnings("unchecked") + FlowablePublishClassic<T> fp = (FlowablePublishClassic<T>) this; + return RxJavaPlugins.onAssembly( + new FlowablePublishAlt<T>(fp.publishSource(), fp.publishBufferSize()) + ); + } + return this; + } + + /** + * Returns a {@code Flowable} that stays connected to this {@code ConnectableFlowable} as long as there + * is at least one subscription to this {@code ConnectableFlowable}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload does not operate on any particular {@link Scheduler}.</dd> + * </dl> * @return a {@link Flowable} * @see <a href="http://reactivex.io/documentation/operators/refcount.html">ReactiveX documentation: RefCount</a> + * @see #refCount(int) + * @see #refCount(long, TimeUnit) + * @see #refCount(int, long, TimeUnit) */ @NonNull + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) public Flowable<T> refCount() { - return RxJavaPlugins.onAssembly(new FlowableRefCount<T>(this)); + return RxJavaPlugins.onAssembly(new FlowableRefCount<T>(onRefCount())); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect if all subscribers have unsubscribed. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload does not operate on any particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable<T> refCount(int subscriberCount) { + return refCount(subscriberCount, 0, TimeUnit.NANOSECONDS, Schedulers.trampoline()); } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Flowable instance + * @see #refCount(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable<T> refCount(long timeout, TimeUnit unit) { + return refCount(1, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the specified {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable<T> refCount(long timeout, TimeUnit unit, Scheduler scheduler) { + return refCount(1, timeout, unit, scheduler); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Flowable instance + * @see #refCount(int, long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable<T> refCount(int subscriberCount, long timeout, TimeUnit unit) { + return refCount(subscriberCount, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the specified {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable<T> refCount(int subscriberCount, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableRefCount<T>(onRefCount(), subscriberCount, timeout, unit, scheduler)); + } + + /** + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable * when the first Subscriber subscribes. + * <p> + * <img width="640" height="392" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.f.png" alt=""> + * <p> + * The connection happens after the first subscription and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. + * <p> + * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. * - * @return an Observable that automatically connects to this ConnectableObservable + * @return a Flowable that automatically connects to this ConnectableFlowable * when the first Subscriber subscribes + * @see #refCount() + * @see #autoConnect(int, Consumer) */ @NonNull public Flowable<T> autoConnect() { return autoConnect(1); } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable * when the specified number of Subscribers subscribe to it. + * <p> + * <img width="640" height="392" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.f.png" alt=""> + * <p> + * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. + * <p> + * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. * * @param numberOfSubscribers the number of subscribers to await before calling connect - * on the ConnectableObservable. A non-positive value indicates + * on the ConnectableFlowable. A non-positive value indicates * an immediate connection. - * @return an Observable that automatically connects to this ConnectableObservable + * @return a Flowable that automatically connects to this ConnectableFlowable * when the specified number of Subscribers subscribe to it */ @NonNull @@ -104,16 +290,24 @@ public Flowable<T> autoConnect(int numberOfSubscribers) { } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable * when the specified number of Subscribers subscribe to it and calls the * specified callback with the Subscription associated with the established connection. + * <p> + * <img width="640" height="392" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.f.png" alt=""> + * <p> + * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. * * @param numberOfSubscribers the number of subscribers to await before calling connect - * on the ConnectableObservable. A non-positive value indicates + * on the ConnectableFlowable. A non-positive value indicates * an immediate connection. * @param connection the callback Consumer that will receive the Subscription representing the * established connection - * @return an Observable that automatically connects to this ConnectableObservable + * @return a Flowable that automatically connects to this ConnectableFlowable * when the specified number of Subscribers subscribe to it and calls the * specified callback with the Subscription associated with the established connection */ diff --git a/src/main/java/io/reactivex/flowables/GroupedFlowable.java b/src/main/java/io/reactivex/flowables/GroupedFlowable.java index 42a51aa03e..d640f4e0e5 100644 --- a/src/main/java/io/reactivex/flowables/GroupedFlowable.java +++ b/src/main/java/io/reactivex/flowables/GroupedFlowable.java @@ -20,7 +20,7 @@ * <p> * <em>Note:</em> A {@link GroupedFlowable} will cache the items it is to emit until such time as it * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those - * {@code GroupedObservable}s that do not concern you. Instead, you can signal to them that they + * {@code GroupedFlowable}s that do not concern you. Instead, you can signal to them that they * may discard their buffers by applying an operator like {@link Flowable#take take}{@code (0)} to them. * * @param <K> @@ -43,9 +43,9 @@ protected GroupedFlowable(@Nullable K key) { } /** - * Returns the key that identifies the group of items emitted by this {@code GroupedObservable}. + * Returns the key that identifies the group of items emitted by this {@code GroupedFlowable}. * - * @return the key that the items emitted by this {@code GroupedObservable} were grouped by + * @return the key that the items emitted by this {@code GroupedFlowable} were grouped by */ @Nullable public K getKey() { diff --git a/src/main/java/io/reactivex/flowables/package-info.java b/src/main/java/io/reactivex/flowables/package-info.java index 6f229e7e37..66f3f48b62 100644 --- a/src/main/java/io/reactivex/flowables/package-info.java +++ b/src/main/java/io/reactivex/flowables/package-info.java @@ -15,7 +15,8 @@ */ /** - * Classes supporting the Flowable base reactive class: connectable and grouped - * flowables. + * Classes supporting the Flowable base reactive class: + * {@link io.reactivex.flowables.ConnectableFlowable} and + * {@link io.reactivex.flowables.GroupedFlowable}. */ package io.reactivex.flowables; diff --git a/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java index e5cc4ad656..446dd6cdf6 100644 --- a/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java @@ -28,7 +28,6 @@ public final class CancellableDisposable extends AtomicReference<Cancellable> implements Disposable { - private static final long serialVersionUID = 5718521705281392066L; public CancellableDisposable(Cancellable cancellable) { diff --git a/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java index f9ad177399..46f13bd743 100644 --- a/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java +++ b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java @@ -20,7 +20,6 @@ import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; - /** * Utility methods for working with Disposables atomically. */ diff --git a/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java index 4ac0d33dbf..4e90491b69 100644 --- a/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java @@ -48,42 +48,41 @@ public boolean isDisposed() { return this == INSTANCE; } - public static void complete(Observer<?> s) { - s.onSubscribe(INSTANCE); - s.onComplete(); + public static void complete(Observer<?> observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); } - public static void complete(MaybeObserver<?> s) { - s.onSubscribe(INSTANCE); - s.onComplete(); + public static void complete(MaybeObserver<?> observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); } - public static void error(Throwable e, Observer<?> s) { - s.onSubscribe(INSTANCE); - s.onError(e); + public static void error(Throwable e, Observer<?> observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); } - public static void complete(CompletableObserver s) { - s.onSubscribe(INSTANCE); - s.onComplete(); + public static void complete(CompletableObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); } - public static void error(Throwable e, CompletableObserver s) { - s.onSubscribe(INSTANCE); - s.onError(e); + public static void error(Throwable e, CompletableObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); } - public static void error(Throwable e, SingleObserver<?> s) { - s.onSubscribe(INSTANCE); - s.onError(e); + public static void error(Throwable e, SingleObserver<?> observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); } - public static void error(Throwable e, MaybeObserver<?> s) { - s.onSubscribe(INSTANCE); - s.onError(e); + public static void error(Throwable e, MaybeObserver<?> observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); } - @Override public boolean offer(Object value) { throw new UnsupportedOperationException("Should not be called!"); @@ -115,5 +114,4 @@ public int requestFusion(int mode) { return mode & ASYNC; } - } diff --git a/src/main/java/io/reactivex/internal/disposables/ObserverFullArbiter.java b/src/main/java/io/reactivex/internal/disposables/ObserverFullArbiter.java deleted file mode 100644 index dd85b6b473..0000000000 --- a/src/main/java/io/reactivex/internal/disposables/ObserverFullArbiter.java +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.disposables; - -import java.util.concurrent.atomic.AtomicInteger; - -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import io.reactivex.internal.queue.SpscLinkedArrayQueue; -import io.reactivex.internal.util.NotificationLite; -import io.reactivex.plugins.RxJavaPlugins; - -/** - * Performs full arbitration of Subscriber events with strict drain (i.e., old emissions of another - * subscriber are dropped). - * - * @param <T> the value type - */ -public final class ObserverFullArbiter<T> extends FullArbiterPad1 implements Disposable { - final Observer<? super T> actual; - final SpscLinkedArrayQueue<Object> queue; - - volatile Disposable s; - - Disposable resource; - - volatile boolean cancelled; - - public ObserverFullArbiter(Observer<? super T> actual, Disposable resource, int capacity) { - this.actual = actual; - this.resource = resource; - this.queue = new SpscLinkedArrayQueue<Object>(capacity); - this.s = EmptyDisposable.INSTANCE; - } - - @Override - public void dispose() { - if (!cancelled) { - cancelled = true; - disposeResource(); - } - } - - @Override - public boolean isDisposed() { - Disposable d = resource; - return d != null ? d.isDisposed() : cancelled; - } - - void disposeResource() { - Disposable d = resource; - resource = null; - if (d != null) { - d.dispose(); - } - } - - public boolean setDisposable(Disposable s) { - if (cancelled) { - return false; - } - - queue.offer(this.s, NotificationLite.disposable(s)); - drain(); - return true; - } - - public boolean onNext(T value, Disposable s) { - if (cancelled) { - return false; - } - - queue.offer(s, NotificationLite.next(value)); - drain(); - return true; - } - - public void onError(Throwable value, Disposable s) { - if (cancelled) { - RxJavaPlugins.onError(value); - return; - } - queue.offer(s, NotificationLite.error(value)); - drain(); - } - - public void onComplete(Disposable s) { - queue.offer(s, NotificationLite.complete()); - drain(); - } - - void drain() { - if (wip.getAndIncrement() != 0) { - return; - } - - int missed = 1; - - final SpscLinkedArrayQueue<Object> q = queue; - final Observer<? super T> a = actual; - - for (;;) { - - for (;;) { - Object o = q.poll(); - if (o == null) { - break; - } - - Object v = q.poll(); - - if (o == s) { - if (NotificationLite.isDisposable(v)) { - Disposable next = NotificationLite.getDisposable(v); - s.dispose(); - if (!cancelled) { - s = next; - } else { - next.dispose(); - } - } else if (NotificationLite.isError(v)) { - q.clear(); - disposeResource(); - - Throwable ex = NotificationLite.getError(v); - if (!cancelled) { - cancelled = true; - a.onError(ex); - } else { - RxJavaPlugins.onError(ex); - } - } else if (NotificationLite.isComplete(v)) { - q.clear(); - disposeResource(); - - if (!cancelled) { - cancelled = true; - a.onComplete(); - } - } else { - a.onNext(NotificationLite.<T>getValue(v)); - } - } - } - - missed = wip.addAndGet(-missed); - if (missed == 0) { - break; - } - } - } -} - -/** Pads the object header away. */ -class FullArbiterPad0 { - volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; - volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; -} - -/** The work-in-progress counter. */ -class FullArbiterWip extends FullArbiterPad0 { - final AtomicInteger wip = new AtomicInteger(); -} - -/** Pads the wip counter away. */ -class FullArbiterPad1 extends FullArbiterWip { - volatile long p1b, p2b, p3b, p4b, p5b, p6b, p7b; - volatile long p8b, p9b, p10b, p11b, p12b, p13b, p14b, p15b; -} diff --git a/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java new file mode 100644 index 0000000000..a111080a77 --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.disposables; + +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.observables.ConnectableObservable; + +/** + * Interface allowing conditional resetting of connections in {@link ConnectableObservable}s + * and {@link ConnectableFlowable}s. + * @since 2.2.2 - experimental + */ +@Experimental +public interface ResettableConnectable { + + /** + * Reset the connectable source only if the given {@link Disposable} {@code connection} instance + * is still representing a connection established by a previous {@code connect()} connection. + * <p> + * For example, an immediately previous connection should reset the connectable source: + * <pre><code> + * Disposable d = connectable.connect(); + * + * ((ResettableConnectable)connectable).resetIf(d); + * </code></pre> + * However, if the connection indicator {@code Disposable} is from a much earlier connection, + * it should not affect the current connection: + * <pre><code> + * Disposable d1 = connectable.connect(); + * d.dispose(); + * + * Disposable d2 = connectable.connect(); + * + * ((ResettableConnectable)connectable).resetIf(d); + * + * assertFalse(d2.isDisposed()); + * </code></pre> + * @param connection the disposable received from a previous {@code connect()} call. + */ + void resetIf(Disposable connection); +} diff --git a/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java index e250bf1925..458194ab63 100644 --- a/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java +++ b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java @@ -28,7 +28,6 @@ public final class SequentialDisposable extends AtomicReference<Disposable> implements Disposable { - private static final long serialVersionUID = -754898800686245608L; /** diff --git a/src/main/java/io/reactivex/internal/functions/Functions.java b/src/main/java/io/reactivex/internal/functions/Functions.java index b54694844f..6aee33fea3 100644 --- a/src/main/java/io/reactivex/internal/functions/Functions.java +++ b/src/main/java/io/reactivex/internal/functions/Functions.java @@ -745,4 +745,23 @@ public void accept(Subscription t) throws Exception { t.request(Long.MAX_VALUE); } } + + @SuppressWarnings("unchecked") + public static <T> Consumer<T> boundedConsumer(int bufferSize) { + return (Consumer<T>) new BoundedConsumer(bufferSize); + } + + public static class BoundedConsumer implements Consumer<Subscription> { + + final int bufferSize; + + BoundedConsumer(int bufferSize) { + this.bufferSize = bufferSize; + } + + @Override + public void accept(Subscription s) throws Exception { + s.request(bufferSize); + } + } } diff --git a/src/main/java/io/reactivex/internal/functions/ObjectHelper.java b/src/main/java/io/reactivex/internal/functions/ObjectHelper.java index b9ce56560e..e77dd2ac0d 100644 --- a/src/main/java/io/reactivex/internal/functions/ObjectHelper.java +++ b/src/main/java/io/reactivex/internal/functions/ObjectHelper.java @@ -71,7 +71,7 @@ public static int compare(int v1, int v2) { } /** - * Compares two integer values similar to Long.compare. + * Compares two long values similar to Long.compare. * @param v1 the first value * @param v2 the second value * @return the comparison result @@ -128,4 +128,17 @@ public boolean test(Object o1, Object o2) { return ObjectHelper.equals(o1, o2); } } + + /** + * Trap null-check attempts on primitives. + * @param value the value to check + * @param message the message to print + * @return the value + * @deprecated this method should not be used as there is no need + * to check primitives for nullness. + */ + @Deprecated + public static long requireNonNull(long value, String message) { + throw new InternalError("Null check on a primitive: " + message); + } } diff --git a/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java index 086e696571..4535b9c18d 100644 --- a/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java +++ b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java @@ -16,7 +16,7 @@ import io.reactivex.FlowableSubscriber; /** - * A Subscriber with an additional onNextIf(T) method that + * A Subscriber with an additional {@link #tryOnNext(Object)} method that * tells the caller the specified value has been accepted or * not. * @@ -30,6 +30,7 @@ public interface ConditionalSubscriber<T> extends FlowableSubscriber<T> { * Conditionally takes the value. * @param t the value to deliver * @return true if the value has been accepted, false if the value has been rejected + * and the next value can be sent immediately */ boolean tryOnNext(T t); } diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java index 850e9284af..f5e803807e 100644 --- a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java +++ b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java @@ -13,7 +13,6 @@ package io.reactivex.internal.fuseable; - /** * Represents a SimpleQueue plus the means and constants for requesting a fusion mode. * @param <T> the value type returned by the SimpleQueue.poll() diff --git a/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java b/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java index 64034616f0..56ec76bb61 100644 --- a/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java @@ -28,13 +28,13 @@ public abstract class BasicFuseableObserver<T, R> implements Observer<T>, QueueDisposable<R> { /** The downstream subscriber. */ - protected final Observer<? super R> actual; + protected final Observer<? super R> downstream; /** The upstream subscription. */ - protected Disposable s; + protected Disposable upstream; /** The upstream's QueueDisposable if not null. */ - protected QueueDisposable<T> qs; + protected QueueDisposable<T> qd; /** Flag indicating no further onXXX event should be accepted. */ protected boolean done; @@ -44,26 +44,26 @@ public abstract class BasicFuseableObserver<T, R> implements Observer<T>, QueueD /** * Construct a BasicFuseableObserver by wrapping the given subscriber. - * @param actual the subscriber, not null (not verified) + * @param downstream the subscriber, not null (not verified) */ - public BasicFuseableObserver(Observer<? super R> actual) { - this.actual = actual; + public BasicFuseableObserver(Observer<? super R> downstream) { + this.downstream = downstream; } // final: fixed protocol steps to support fuseable and non-fuseable upstream @SuppressWarnings("unchecked") @Override - public final void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { + public final void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { - this.s = s; - if (s instanceof QueueDisposable) { - this.qs = (QueueDisposable<T>)s; + this.upstream = d; + if (d instanceof QueueDisposable) { + this.qd = (QueueDisposable<T>)d; } if (beforeDownstream()) { - actual.onSubscribe(this); + downstream.onSubscribe(this); afterDownstream(); } @@ -97,7 +97,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } /** @@ -106,7 +106,7 @@ public void onError(Throwable t) { */ protected final void fail(Throwable t) { Exceptions.throwIfFatal(t); - s.dispose(); + upstream.dispose(); onError(t); } @@ -116,7 +116,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } /** @@ -124,16 +124,16 @@ public void onComplete() { * saves the established mode in {@link #sourceMode} if that mode doesn't * have the {@link QueueDisposable#BOUNDARY} flag set. * <p> - * If the upstream doesn't support fusion ({@link #qs} is null), the method + * If the upstream doesn't support fusion ({@link #qd} is null), the method * returns {@link QueueDisposable#NONE}. * @param mode the fusion mode requested * @return the established fusion mode */ protected final int transitiveBoundaryFusion(int mode) { - QueueDisposable<T> qs = this.qs; - if (qs != null) { + QueueDisposable<T> qd = this.qd; + if (qd != null) { if ((mode & BOUNDARY) == 0) { - int m = qs.requestFusion(mode); + int m = qd.requestFusion(mode); if (m != NONE) { sourceMode = m; } @@ -149,22 +149,22 @@ protected final int transitiveBoundaryFusion(int mode) { @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override public boolean isEmpty() { - return qs.isEmpty(); + return qd.isEmpty(); } @Override public void clear() { - qs.clear(); + qd.clear(); } // ----------------------------------------------------------- diff --git a/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java index 39d68c80dc..a5d2adf430 100644 --- a/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java +++ b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java @@ -26,7 +26,6 @@ public abstract class BasicIntQueueDisposable<T> extends AtomicInteger implements QueueDisposable<T> { - private static final long serialVersionUID = -1001730202384742097L; @Override diff --git a/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java index e14425f70e..188c78b1b3 100644 --- a/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java @@ -26,7 +26,6 @@ public final class BiConsumerSingleObserver<T> extends AtomicReference<Disposable> implements SingleObserver<T>, Disposable { - private static final long serialVersionUID = 4943102778943297569L; final BiConsumer<? super T, ? super Throwable> onCallback; diff --git a/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java index 4efb537e08..63078f8e68 100644 --- a/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java @@ -24,7 +24,7 @@ public abstract class BlockingBaseObserver<T> extends CountDownLatch T value; Throwable error; - Disposable d; + Disposable upstream; volatile boolean cancelled; @@ -34,7 +34,7 @@ public BlockingBaseObserver() { @Override public final void onSubscribe(Disposable d) { - this.d = d; + this.upstream = d; if (cancelled) { d.dispose(); } @@ -48,7 +48,7 @@ public final void onComplete() { @Override public final void dispose() { cancelled = true; - Disposable d = this.d; + Disposable d = this.upstream; if (d != null) { d.dispose(); } diff --git a/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java index 2927679aff..212edb6bdb 100644 --- a/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java @@ -24,7 +24,7 @@ public final class BlockingFirstObserver<T> extends BlockingBaseObserver<T> { public void onNext(T t) { if (value == null) { value = t; - d.dispose(); + upstream.dispose(); countDown(); } } diff --git a/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java index 934123e381..2b5f5603e0 100644 --- a/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java @@ -19,6 +19,8 @@ import io.reactivex.disposables.Disposable; import io.reactivex.internal.util.*; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * A combined Observer that awaits the success or error signal via a CountDownLatch. * @param <T> the value type @@ -30,7 +32,7 @@ public final class BlockingMultiObserver<T> T value; Throwable error; - Disposable d; + Disposable upstream; volatile boolean cancelled; @@ -40,7 +42,7 @@ public BlockingMultiObserver() { void dispose() { cancelled = true; - Disposable d = this.d; + Disposable d = this.upstream; if (d != null) { d.dispose(); } @@ -48,7 +50,7 @@ void dispose() { @Override public void onSubscribe(Disposable d) { - this.d = d; + this.upstream = d; if (cancelled) { d.dispose(); } @@ -148,7 +150,7 @@ public Throwable blockingGetError(long timeout, TimeUnit unit) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { dispose(); - throw ExceptionHelper.wrapOrThrow(new TimeoutException()); + throw ExceptionHelper.wrapOrThrow(new TimeoutException(timeoutMessage(timeout, unit))); } } catch (InterruptedException ex) { dispose(); diff --git a/src/main/java/io/reactivex/internal/observers/BlockingObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingObserver.java index 3604ad0351..4731dc380d 100644 --- a/src/main/java/io/reactivex/internal/observers/BlockingObserver.java +++ b/src/main/java/io/reactivex/internal/observers/BlockingObserver.java @@ -34,8 +34,8 @@ public BlockingObserver(Queue<Object> queue) { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override diff --git a/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java index cfb758552a..ee059c5b37 100644 --- a/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java @@ -20,11 +20,12 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; import io.reactivex.plugins.RxJavaPlugins; public final class CallbackCompletableObserver -extends AtomicReference<Disposable> implements CompletableObserver, Disposable, Consumer<Throwable> { - +extends AtomicReference<Disposable> + implements CompletableObserver, Disposable, Consumer<Throwable>, LambdaConsumerIntrospection { private static final long serialVersionUID = -4361286194466301354L; @@ -82,4 +83,9 @@ public void dispose() { public boolean isDisposed() { return get() == DisposableHelper.DISPOSED; } + + @Override + public boolean hasCustomOnError() { + return onError != this; + } } diff --git a/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java index 525543838e..59735e9c11 100644 --- a/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java @@ -20,12 +20,13 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.Consumer; import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; import io.reactivex.plugins.RxJavaPlugins; public final class ConsumerSingleObserver<T> extends AtomicReference<Disposable> -implements SingleObserver<T>, Disposable { - +implements SingleObserver<T>, Disposable, LambdaConsumerIntrospection { private static final long serialVersionUID = -7012088219455310787L; @@ -74,4 +75,9 @@ public void dispose() { public boolean isDisposed() { return get() == DisposableHelper.DISPOSED; } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } } diff --git a/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java b/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java index 8dd5084d7e..a975f0aecc 100644 --- a/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java +++ b/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java @@ -27,7 +27,7 @@ public class DeferredScalarDisposable<T> extends BasicIntQueueDisposable<T> { private static final long serialVersionUID = -5502432239815349361L; /** The target of the events. */ - protected final Observer<? super T> actual; + protected final Observer<? super T> downstream; /** The value stored temporarily when in fusion mode. */ protected T value; @@ -47,10 +47,10 @@ public class DeferredScalarDisposable<T> extends BasicIntQueueDisposable<T> { /** * Constructs a DeferredScalarDisposable by wrapping the Observer. - * @param actual the Observer to wrap, not null (not verified) + * @param downstream the Observer to wrap, not null (not verified) */ - public DeferredScalarDisposable(Observer<? super T> actual) { - this.actual = actual; + public DeferredScalarDisposable(Observer<? super T> downstream) { + this.downstream = downstream; } @Override @@ -72,14 +72,15 @@ public final void complete(T value) { if ((state & (FUSED_READY | FUSED_CONSUMED | TERMINATED | DISPOSED)) != 0) { return; } + Observer<? super T> a = downstream; if (state == FUSED_EMPTY) { this.value = value; lazySet(FUSED_READY); + a.onNext(null); } else { lazySet(TERMINATED); + a.onNext(value); } - Observer<? super T> a = actual; - a.onNext(value); if (get() != DISPOSED) { a.onComplete(); } @@ -96,7 +97,7 @@ public final void error(Throwable t) { return; } lazySet(TERMINATED); - actual.onError(t); + downstream.onError(t); } /** @@ -108,7 +109,7 @@ public final void complete() { return; } lazySet(TERMINATED); - actual.onComplete(); + downstream.onComplete(); } @Nullable diff --git a/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java b/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java index 4e5319abf4..91236b37c2 100644 --- a/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java +++ b/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java @@ -29,22 +29,22 @@ public abstract class DeferredScalarObserver<T, R> private static final long serialVersionUID = -266195175408988651L; /** The upstream disposable. */ - protected Disposable s; + protected Disposable upstream; /** * Creates a DeferredScalarObserver instance and wraps a downstream Observer. - * @param actual the downstream subscriber, not null (not verified) + * @param downstream the downstream subscriber, not null (not verified) */ - public DeferredScalarObserver(Observer<? super R> actual) { - super(actual); + public DeferredScalarObserver(Observer<? super R> downstream) { + super(downstream); } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -68,6 +68,6 @@ public void onComplete() { @Override public void dispose() { super.dispose(); - s.dispose(); + upstream.dispose(); } } diff --git a/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java index 3ba9ebfa68..59d7fac2bc 100644 --- a/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java +++ b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java @@ -21,47 +21,48 @@ import io.reactivex.plugins.RxJavaPlugins; public final class DisposableLambdaObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Consumer<? super Disposable> onSubscribe; final Action onDispose; - Disposable s; + Disposable upstream; public DisposableLambdaObserver(Observer<? super T> actual, Consumer<? super Disposable> onSubscribe, Action onDispose) { - this.actual = actual; + this.downstream = actual; this.onSubscribe = onSubscribe; this.onDispose = onDispose; } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { // this way, multiple calls to onSubscribe can show up in tests that use doOnSubscribe to validate behavior try { - onSubscribe.accept(s); + onSubscribe.accept(d); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); - this.s = DisposableHelper.DISPOSED; - EmptyDisposable.error(e, actual); + d.dispose(); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(e, downstream); return; } - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - if (s != DisposableHelper.DISPOSED) { - actual.onError(t); + if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -69,25 +70,29 @@ public void onError(Throwable t) { @Override public void onComplete() { - if (s != DisposableHelper.DISPOSED) { - actual.onComplete(); + if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } } - @Override public void dispose() { - try { - onDispose.run(); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - RxJavaPlugins.onError(e); + Disposable d = upstream; + if (d != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + try { + onDispose.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + d.dispose(); } - s.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } diff --git a/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java index 64e295566c..38d0f3746e 100644 --- a/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java @@ -19,12 +19,12 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.OnErrorNotImplementedException; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; import io.reactivex.plugins.RxJavaPlugins; public final class EmptyCompletableObserver extends AtomicReference<Disposable> -implements CompletableObserver, Disposable { - +implements CompletableObserver, Disposable, LambdaConsumerIntrospection { private static final long serialVersionUID = -7545121636549663526L; @@ -55,4 +55,8 @@ public void onSubscribe(Disposable d) { DisposableHelper.setOnce(this, d); } + @Override + public boolean hasCustomOnError() { + return false; + } } diff --git a/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java index aaac865500..22ba3f80a8 100644 --- a/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java +++ b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java @@ -26,7 +26,6 @@ public final class ForEachWhileObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable { - private static final long serialVersionUID = -4403180040475402120L; final Predicate<? super T> onNext; @@ -45,8 +44,8 @@ public ForEachWhileObserver(Predicate<? super T> onNext, } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override diff --git a/src/main/java/io/reactivex/internal/observers/FullArbiterObserver.java b/src/main/java/io/reactivex/internal/observers/FullArbiterObserver.java deleted file mode 100644 index 79b1bd723d..0000000000 --- a/src/main/java/io/reactivex/internal/observers/FullArbiterObserver.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.observers; - -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.*; - -/** - * Subscriber that communicates with a FullArbiter. - * - * @param <T> the value type - */ -public final class FullArbiterObserver<T> implements Observer<T> { - final ObserverFullArbiter<T> arbiter; - - Disposable s; - - public FullArbiterObserver(ObserverFullArbiter<T> arbiter) { - this.arbiter = arbiter; - } - - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - arbiter.setDisposable(s); - } - } - - @Override - public void onNext(T t) { - arbiter.onNext(t, s); - } - - @Override - public void onError(Throwable t) { - arbiter.onError(t, s); - } - - @Override - public void onComplete() { - arbiter.onComplete(s); - } -} diff --git a/src/main/java/io/reactivex/internal/observers/FutureObserver.java b/src/main/java/io/reactivex/internal/observers/FutureObserver.java index 48f6ef0971..9b0d12140a 100644 --- a/src/main/java/io/reactivex/internal/observers/FutureObserver.java +++ b/src/main/java/io/reactivex/internal/observers/FutureObserver.java @@ -23,6 +23,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * An Observer + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -35,22 +37,22 @@ public final class FutureObserver<T> extends CountDownLatch T value; Throwable error; - final AtomicReference<Disposable> s; + final AtomicReference<Disposable> upstream; public FutureObserver() { super(1); - this.s = new AtomicReference<Disposable>(); + this.upstream = new AtomicReference<Disposable>(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { for (;;) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == this || a == DisposableHelper.DISPOSED) { return false; } - if (s.compareAndSet(a, DisposableHelper.DISPOSED)) { + if (upstream.compareAndSet(a, DisposableHelper.DISPOSED)) { if (a != null) { a.dispose(); } @@ -62,7 +64,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Override public boolean isCancelled() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } @Override @@ -92,7 +94,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } @@ -108,14 +110,14 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override public void onNext(T t) { if (value != null) { - s.get().dispose(); + upstream.get().dispose(); onError(new IndexOutOfBoundsException("More than one element received")); return; } @@ -128,12 +130,12 @@ public void onError(Throwable t) { error = t; for (;;) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == this || a == DisposableHelper.DISPOSED) { RxJavaPlugins.onError(t); return; } - if (s.compareAndSet(a, this)) { + if (upstream.compareAndSet(a, this)) { countDown(); return; } @@ -150,11 +152,11 @@ public void onComplete() { return; } for (;;) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == this || a == DisposableHelper.DISPOSED) { return; } - if (s.compareAndSet(a, this)) { + if (upstream.compareAndSet(a, this)) { countDown(); return; } diff --git a/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java index 33b5b8099a..1ad8242b5c 100644 --- a/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java @@ -22,6 +22,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * An Observer + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -34,22 +36,22 @@ public final class FutureSingleObserver<T> extends CountDownLatch T value; Throwable error; - final AtomicReference<Disposable> s; + final AtomicReference<Disposable> upstream; public FutureSingleObserver() { super(1); - this.s = new AtomicReference<Disposable>(); + this.upstream = new AtomicReference<Disposable>(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { for (;;) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == this || a == DisposableHelper.DISPOSED) { return false; } - if (s.compareAndSet(a, DisposableHelper.DISPOSED)) { + if (upstream.compareAndSet(a, DisposableHelper.DISPOSED)) { if (a != null) { a.dispose(); } @@ -61,7 +63,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Override public boolean isCancelled() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } @Override @@ -91,7 +93,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } @@ -107,31 +109,31 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override public void onSuccess(T t) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == DisposableHelper.DISPOSED) { return; } value = t; - s.compareAndSet(a, this); + upstream.compareAndSet(a, this); countDown(); } @Override public void onError(Throwable t) { for (;;) { - Disposable a = s.get(); + Disposable a = upstream.get(); if (a == DisposableHelper.DISPOSED) { RxJavaPlugins.onError(t); return; } error = t; - if (s.compareAndSet(a, this)) { + if (upstream.compareAndSet(a, this)) { countDown(); return; } diff --git a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java index 3588db20ef..3a18b38153 100644 --- a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java +++ b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java @@ -31,7 +31,6 @@ public final class InnerQueuedObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable { - private static final long serialVersionUID = -5417183359794346637L; final InnerQueuedObserverSupport<T> parent; @@ -50,23 +49,23 @@ public InnerQueuedObserver(InnerQueuedObserverSupport<T> parent, int prefetch) { } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.setOnce(this, s)) { - if (s instanceof QueueDisposable) { + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") - QueueDisposable<T> qs = (QueueDisposable<T>) s; + QueueDisposable<T> qd = (QueueDisposable<T>) d; - int m = qs.requestFusion(QueueDisposable.ANY); + int m = qd.requestFusion(QueueDisposable.ANY); if (m == QueueSubscription.SYNC) { fusionMode = m; - queue = qs; + queue = qd; done = true; parent.innerComplete(this); return; } if (m == QueueDisposable.ASYNC) { fusionMode = m; - queue = qs; + queue = qd; return; } } diff --git a/src/main/java/io/reactivex/internal/observers/LambdaObserver.java b/src/main/java/io/reactivex/internal/observers/LambdaObserver.java index 9f06ecac75..7549910637 100644 --- a/src/main/java/io/reactivex/internal/observers/LambdaObserver.java +++ b/src/main/java/io/reactivex/internal/observers/LambdaObserver.java @@ -20,9 +20,12 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; import io.reactivex.plugins.RxJavaPlugins; -public final class LambdaObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable { +public final class LambdaObserver<T> extends AtomicReference<Disposable> + implements Observer<T>, Disposable, LambdaConsumerIntrospection { private static final long serialVersionUID = -7251123623727029452L; final Consumer<? super T> onNext; @@ -41,13 +44,13 @@ public LambdaObserver(Consumer<? super T> onNext, Consumer<? super Throwable> on } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.setOnce(this, s)) { + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { try { onSubscribe.accept(this); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.dispose(); + d.dispose(); onError(ex); } } @@ -76,6 +79,8 @@ public void onError(Throwable t) { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(new CompositeException(t, e)); } + } else { + RxJavaPlugins.onError(t); } } @@ -101,4 +106,9 @@ public void dispose() { public boolean isDisposed() { return get() == DisposableHelper.DISPOSED; } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } } diff --git a/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java b/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java index d906317a2d..ab24a709f0 100644 --- a/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java +++ b/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java @@ -29,7 +29,7 @@ * @param <V> the value type the child subscriber accepts */ public abstract class QueueDrainObserver<T, U, V> extends QueueDrainSubscriberPad2 implements Observer<T>, ObservableQueueDrain<U, V> { - protected final Observer<? super V> actual; + protected final Observer<? super V> downstream; protected final SimplePlainQueue<U> queue; protected volatile boolean cancelled; @@ -38,7 +38,7 @@ public abstract class QueueDrainObserver<T, U, V> extends QueueDrainSubscriberPa protected Throwable error; public QueueDrainObserver(Observer<? super V> actual, SimplePlainQueue<U> queue) { - this.actual = actual; + this.downstream = actual; this.queue = queue; } @@ -62,11 +62,11 @@ public final boolean fastEnter() { } protected final void fastPathEmit(U value, boolean delayError, Disposable dispose) { - final Observer<? super V> s = actual; + final Observer<? super V> observer = downstream; final SimplePlainQueue<U> q = queue; if (wip.get() == 0 && wip.compareAndSet(0, 1)) { - accept(s, value); + accept(observer, value); if (leave(-1) == 0) { return; } @@ -76,7 +76,7 @@ protected final void fastPathEmit(U value, boolean delayError, Disposable dispos return; } } - QueueDrainHelper.drainLoop(q, s, delayError, dispose, this); + QueueDrainHelper.drainLoop(q, observer, delayError, dispose, this); } /** @@ -86,12 +86,12 @@ protected final void fastPathEmit(U value, boolean delayError, Disposable dispos * @param disposable the resource to dispose if the drain terminates */ protected final void fastPathOrderedEmit(U value, boolean delayError, Disposable disposable) { - final Observer<? super V> s = actual; + final Observer<? super V> observer = downstream; final SimplePlainQueue<U> q = queue; if (wip.get() == 0 && wip.compareAndSet(0, 1)) { if (q.isEmpty()) { - accept(s, value); + accept(observer, value); if (leave(-1) == 0) { return; } @@ -104,7 +104,7 @@ protected final void fastPathOrderedEmit(U value, boolean delayError, Disposable return; } } - QueueDrainHelper.drainLoop(q, s, delayError, disposable, this); + QueueDrainHelper.drainLoop(q, observer, delayError, disposable, this); } @Override diff --git a/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java b/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java index 980b4335d9..1dc1c5fa29 100644 --- a/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java +++ b/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java @@ -29,11 +29,11 @@ public final class ResumeSingleObserver<T> implements SingleObserver<T> { final AtomicReference<Disposable> parent; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; - public ResumeSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super T> actual) { + public ResumeSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super T> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -43,11 +43,11 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java index aa0f10c126..8a5255d937 100644 --- a/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java +++ b/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java @@ -22,10 +22,10 @@ public final class SubscriberCompletableObserver<T> implements CompletableObserver, Subscription { final Subscriber<? super T> subscriber; - Disposable d; + Disposable upstream; - public SubscriberCompletableObserver(Subscriber<? super T> observer) { - this.subscriber = observer; + public SubscriberCompletableObserver(Subscriber<? super T> subscriber) { + this.subscriber = subscriber; } @Override @@ -40,8 +40,8 @@ public void onError(Throwable e) { @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; subscriber.onSubscribe(this); } @@ -54,6 +54,6 @@ public void request(long n) { @Override public void cancel() { - d.dispose(); + upstream.dispose(); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java index 82d0abbcc5..7de1c648e4 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java @@ -31,7 +31,7 @@ public CompletableAmb(CompletableSource[] sources, Iterable<? extends Completabl } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { CompletableSource[] sources = this.sources; int count = 0; if (sources == null) { @@ -39,7 +39,7 @@ public void subscribeActual(final CompletableObserver s) { try { for (CompletableSource element : sourcesIterable) { if (element == null) { - EmptyDisposable.error(new NullPointerException("One of the sources is null"), s); + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); return; } if (count == sources.length) { @@ -51,7 +51,7 @@ public void subscribeActual(final CompletableObserver s) { } } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } } else { @@ -59,12 +59,10 @@ public void subscribeActual(final CompletableObserver s) { } final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + observer.onSubscribe(set); final AtomicBoolean once = new AtomicBoolean(); - CompletableObserver inner = new Amb(once, set, s); - for (int i = 0; i < count; i++) { CompletableSource c = sources[i]; if (set.isDisposed()) { @@ -74,7 +72,7 @@ public void subscribeActual(final CompletableObserver s) { NullPointerException npe = new NullPointerException("One of the sources is null"); if (once.compareAndSet(false, true)) { set.dispose(); - s.onError(npe); + observer.onError(npe); } else { RxJavaPlugins.onError(npe); } @@ -82,38 +80,45 @@ public void subscribeActual(final CompletableObserver s) { } // no need to have separate subscribers because inner is stateless - c.subscribe(inner); + c.subscribe(new Amb(once, set, observer)); } if (count == 0) { - s.onComplete(); + observer.onComplete(); } } static final class Amb implements CompletableObserver { - private final AtomicBoolean once; - private final CompositeDisposable set; - private final CompletableObserver s; - Amb(AtomicBoolean once, CompositeDisposable set, CompletableObserver s) { + final AtomicBoolean once; + + final CompositeDisposable set; + + final CompletableObserver downstream; + + Disposable upstream; + + Amb(AtomicBoolean once, CompositeDisposable set, CompletableObserver observer) { this.once = once; this.set = set; - this.s = s; + this.downstream = observer; } @Override public void onComplete() { if (once.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - s.onComplete(); + downstream.onComplete(); } } @Override public void onError(Throwable e) { if (once.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - s.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -121,8 +126,8 @@ public void onError(Throwable e) { @Override public void onSubscribe(Disposable d) { + upstream = d; set.add(d); } - } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java new file mode 100644 index 0000000000..ff7b6cced5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class CompletableAndThenCompletable extends Completable { + + final CompletableSource source; + + final CompletableSource next; + + public CompletableAndThenCompletable(CompletableSource source, CompletableSource next) { + this.source = source; + this.next = next; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new SourceObserver(observer, next)); + } + + static final class SourceObserver + extends AtomicReference<Disposable> + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = -4101678820158072998L; + + final CompletableObserver actualObserver; + + final CompletableSource next; + + SourceObserver(CompletableObserver actualObserver, CompletableSource next) { + this.actualObserver = actualObserver; + this.next = next; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + actualObserver.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + actualObserver.onError(e); + } + + @Override + public void onComplete() { + next.subscribe(new NextObserver(this, actualObserver)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class NextObserver implements CompletableObserver { + + final AtomicReference<Disposable> parent; + + final CompletableObserver downstream; + + NextObserver(AtomicReference<Disposable> parent, CompletableObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java index b3e0aee7fa..ef1cc3c65f 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java @@ -16,15 +16,13 @@ import java.util.concurrent.atomic.*; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; /** * Consume the upstream source exactly once and cache its terminal event. - * - * @since 2.0.4 - experimental + * <p>History: 2.0.4 - experimental + * @since 2.1 */ -@Experimental public final class CompletableCache extends Completable implements CompletableObserver { static final InnerCompletableCache[] EMPTY = new InnerCompletableCache[0]; @@ -46,9 +44,9 @@ public CompletableCache(CompletableSource source) { } @Override - protected void subscribeActual(CompletableObserver s) { - InnerCompletableCache inner = new InnerCompletableCache(s); - s.onSubscribe(inner); + protected void subscribeActual(CompletableObserver observer) { + InnerCompletableCache inner = new InnerCompletableCache(observer); + observer.onSubscribe(inner); if (add(inner)) { if (inner.isDisposed()) { @@ -61,9 +59,9 @@ protected void subscribeActual(CompletableObserver s) { } else { Throwable ex = error; if (ex != null) { - s.onError(ex); + observer.onError(ex); } else { - s.onComplete(); + observer.onComplete(); } } } @@ -78,7 +76,7 @@ public void onError(Throwable e) { error = e; for (InnerCompletableCache inner : observers.getAndSet(TERMINATED)) { if (!inner.get()) { - inner.actual.onError(e); + inner.downstream.onError(e); } } } @@ -87,7 +85,7 @@ public void onError(Throwable e) { public void onComplete() { for (InnerCompletableCache inner : observers.getAndSet(TERMINATED)) { if (!inner.get()) { - inner.actual.onComplete(); + inner.downstream.onComplete(); } } } @@ -151,10 +149,10 @@ final class InnerCompletableCache private static final long serialVersionUID = 8943152917179642732L; - final CompletableObserver actual; + final CompletableObserver downstream; - InnerCompletableCache(CompletableObserver actual) { - this.actual = actual; + InnerCompletableCache(CompletableObserver downstream) { + this.downstream = downstream; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java index fadf4b422e..cf52bf9755 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java @@ -36,8 +36,8 @@ public CompletableConcat(Publisher<? extends CompletableSource> sources, int pre } @Override - public void subscribeActual(CompletableObserver s) { - sources.subscribe(new CompletableConcatSubscriber(s, prefetch)); + public void subscribeActual(CompletableObserver observer) { + sources.subscribe(new CompletableConcatSubscriber(observer, prefetch)); } static final class CompletableConcatSubscriber @@ -45,7 +45,7 @@ static final class CompletableConcatSubscriber implements FlowableSubscriber<CompletableSource>, Disposable { private static final long serialVersionUID = 9032184911934499404L; - final CompletableObserver actual; + final CompletableObserver downstream; final int prefetch; @@ -61,14 +61,14 @@ static final class CompletableConcatSubscriber SimpleQueue<CompletableSource> queue; - Subscription s; + Subscription upstream; volatile boolean done; volatile boolean active; CompletableConcatSubscriber(CompletableObserver actual, int prefetch) { - this.actual = actual; + this.downstream = actual; this.prefetch = prefetch; this.inner = new ConcatInnerObserver(this); this.once = new AtomicBoolean(); @@ -77,8 +77,8 @@ static final class CompletableConcatSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; long r = prefetch == Integer.MAX_VALUE ? Long.MAX_VALUE : prefetch; @@ -92,14 +92,14 @@ public void onSubscribe(Subscription s) { sourceFused = m; queue = qs; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); drain(); return; } if (m == QueueSubscription.ASYNC) { sourceFused = m; queue = qs; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(r); return; } @@ -111,7 +111,7 @@ public void onSubscribe(Subscription s) { queue = new SpscArrayQueue<CompletableSource>(prefetch); } - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(r); } @@ -132,7 +132,7 @@ public void onNext(CompletableSource t) { public void onError(Throwable t) { if (once.compareAndSet(false, true)) { DisposableHelper.dispose(inner); - actual.onError(t); + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -146,7 +146,7 @@ public void onComplete() { @Override public void dispose() { - s.cancel(); + upstream.cancel(); DisposableHelper.dispose(inner); } @@ -183,7 +183,7 @@ void drain() { if (d && empty) { if (once.compareAndSet(false, true)) { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -206,7 +206,7 @@ void request() { int p = consumed + 1; if (p == limit) { consumed = 0; - s.request(p); + upstream.request(p); } else { consumed = p; } @@ -215,8 +215,8 @@ void request() { void innerError(Throwable e) { if (once.compareAndSet(false, true)) { - s.cancel(); - actual.onError(e); + upstream.cancel(); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java index caccf81c79..6bcf9d5516 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java @@ -27,9 +27,9 @@ public CompletableConcatArray(CompletableSource[] sources) { } @Override - public void subscribeActual(CompletableObserver s) { - ConcatInnerObserver inner = new ConcatInnerObserver(s, sources); - s.onSubscribe(inner.sd); + public void subscribeActual(CompletableObserver observer) { + ConcatInnerObserver inner = new ConcatInnerObserver(observer, sources); + observer.onSubscribe(inner.sd); inner.next(); } @@ -37,7 +37,7 @@ static final class ConcatInnerObserver extends AtomicInteger implements Completa private static final long serialVersionUID = -7965400327305809232L; - final CompletableObserver actual; + final CompletableObserver downstream; final CompletableSource[] sources; int index; @@ -45,19 +45,19 @@ static final class ConcatInnerObserver extends AtomicInteger implements Completa final SequentialDisposable sd; ConcatInnerObserver(CompletableObserver actual, CompletableSource[] sources) { - this.actual = actual; + this.downstream = actual; this.sources = sources; this.sd = new SequentialDisposable(); } @Override public void onSubscribe(Disposable d) { - sd.update(d); + sd.replace(d); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override @@ -82,7 +82,7 @@ void next() { int idx = index++; if (idx == a.length) { - actual.onComplete(); + downstream.onComplete(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java index 624047a627..acc2d12fd0 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java @@ -30,7 +30,7 @@ public CompletableConcatIterable(Iterable<? extends CompletableSource> sources) } @Override - public void subscribeActual(CompletableObserver s) { + public void subscribeActual(CompletableObserver observer) { Iterator<? extends CompletableSource> it; @@ -38,12 +38,12 @@ public void subscribeActual(CompletableObserver s) { it = ObjectHelper.requireNonNull(sources.iterator(), "The iterator returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - ConcatInnerObserver inner = new ConcatInnerObserver(s, it); - s.onSubscribe(inner.sd); + ConcatInnerObserver inner = new ConcatInnerObserver(observer, it); + observer.onSubscribe(inner.sd); inner.next(); } @@ -51,25 +51,25 @@ static final class ConcatInnerObserver extends AtomicInteger implements Completa private static final long serialVersionUID = -7965400327305809232L; - final CompletableObserver actual; + final CompletableObserver downstream; final Iterator<? extends CompletableSource> sources; final SequentialDisposable sd; ConcatInnerObserver(CompletableObserver actual, Iterator<? extends CompletableSource> sources) { - this.actual = actual; + this.downstream = actual; this.sources = sources; this.sd = new SequentialDisposable(); } @Override public void onSubscribe(Disposable d) { - sd.update(d); + sd.replace(d); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override @@ -97,12 +97,12 @@ void next() { b = a.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } if (!b) { - actual.onComplete(); + downstream.onComplete(); return; } @@ -112,7 +112,7 @@ void next() { c = ObjectHelper.requireNonNull(a.next(), "The CompletableSource returned is null"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java index c9035e7033..1e2bc74f79 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java @@ -31,9 +31,9 @@ public CompletableCreate(CompletableOnSubscribe source) { } @Override - protected void subscribeActual(CompletableObserver s) { - Emitter parent = new Emitter(s); - s.onSubscribe(parent); + protected void subscribeActual(CompletableObserver observer) { + Emitter parent = new Emitter(observer); + observer.onSubscribe(parent); try { source.subscribe(parent); @@ -49,10 +49,10 @@ static final class Emitter private static final long serialVersionUID = -2467358622224974244L; - final CompletableObserver actual; + final CompletableObserver downstream; - Emitter(CompletableObserver actual) { - this.actual = actual; + Emitter(CompletableObserver downstream) { + this.downstream = downstream; } @Override @@ -61,7 +61,7 @@ public void onComplete() { Disposable d = getAndSet(DisposableHelper.DISPOSED); if (d != DisposableHelper.DISPOSED) { try { - actual.onComplete(); + downstream.onComplete(); } finally { if (d != null) { d.dispose(); @@ -87,7 +87,7 @@ public boolean tryOnError(Throwable t) { Disposable d = getAndSet(DisposableHelper.DISPOSED); if (d != DisposableHelper.DISPOSED) { try { - actual.onError(t); + downstream.onError(t); } finally { if (d != null) { d.dispose(); @@ -118,5 +118,10 @@ public void dispose() { public boolean isDisposed() { return DisposableHelper.isDisposed(get()); } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java index b486a3095f..030ca4fd3d 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java @@ -29,18 +29,18 @@ public CompletableDefer(Callable<? extends CompletableSource> completableSupplie } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { CompletableSource c; try { c = ObjectHelper.requireNonNull(completableSupplier.call(), "The completableSupplier returned a null CompletableSource"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - c.subscribe(s); + c.subscribe(observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java index fa600c64b1..a23fd003f1 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java @@ -14,9 +14,11 @@ package io.reactivex.internal.operators.completable; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import io.reactivex.*; -import io.reactivex.disposables.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; public final class CompletableDelay extends Completable { @@ -39,55 +41,71 @@ public CompletableDelay(CompletableSource source, long delay, TimeUnit unit, Sch } @Override - protected void subscribeActual(final CompletableObserver s) { - final CompositeDisposable set = new CompositeDisposable(); - - source.subscribe(new Delay(set, s)); + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new Delay(observer, delay, unit, scheduler, delayError)); } - final class Delay implements CompletableObserver { + static final class Delay extends AtomicReference<Disposable> + implements CompletableObserver, Runnable, Disposable { + + private static final long serialVersionUID = 465972761105851022L; + + final CompletableObserver downstream; + + final long delay; + + final TimeUnit unit; - private final CompositeDisposable set; - final CompletableObserver s; + final Scheduler scheduler; - Delay(CompositeDisposable set, CompletableObserver s) { - this.set = set; - this.s = s; + final boolean delayError; + + Throwable error; + + Delay(CompletableObserver downstream, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + this.downstream = downstream; + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } } @Override public void onComplete() { - set.add(scheduler.scheduleDirect(new OnComplete(), delay, unit)); + DisposableHelper.replace(this, scheduler.scheduleDirect(this, delay, unit)); } @Override public void onError(final Throwable e) { - set.add(scheduler.scheduleDirect(new OnError(e), delayError ? delay : 0, unit)); + error = e; + DisposableHelper.replace(this, scheduler.scheduleDirect(this, delayError ? delay : 0, unit)); } @Override - public void onSubscribe(Disposable d) { - set.add(d); - s.onSubscribe(set); + public void dispose() { + DisposableHelper.dispose(this); } - final class OnComplete implements Runnable { - @Override - public void run() { - s.onComplete(); - } + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); } - final class OnError implements Runnable { - private final Throwable e; - - OnError(Throwable e) { - this.e = e; - } - - @Override - public void run() { - s.onError(e); + @Override + public void run() { + Throwable e = error; + error = null; + if (e != null) { + downstream.onError(e); + } else { + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java new file mode 100644 index 0000000000..f19a0a045d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Breaks the references between the upstream and downstream when the Completable terminates. + * <p>History: 2.1.5 - experimental + * @since 2.2 + */ +public final class CompletableDetach extends Completable { + + final CompletableSource source; + + public CompletableDetach(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new DetachCompletableObserver(observer)); + } + + static final class DetachCompletableObserver implements CompletableObserver, Disposable { + + CompletableObserver downstream; + + Disposable upstream; + + DetachCompletableObserver(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + CompletableObserver a = downstream; + if (a != null) { + downstream = null; + a.onError(e); + } + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + CompletableObserver a = downstream; + if (a != null) { + downstream = null; + a.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java index f8e53d3253..904894df7c 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java @@ -30,21 +30,21 @@ public CompletableDisposeOn(CompletableSource source, Scheduler scheduler) { } @Override - protected void subscribeActual(final CompletableObserver s) { - source.subscribe(new CompletableObserverImplementation(s, scheduler)); + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new DisposeOnObserver(observer, scheduler)); } - static final class CompletableObserverImplementation implements CompletableObserver, Disposable, Runnable { - final CompletableObserver s; + static final class DisposeOnObserver implements CompletableObserver, Disposable, Runnable { + final CompletableObserver downstream; final Scheduler scheduler; - Disposable d; + Disposable upstream; volatile boolean disposed; - CompletableObserverImplementation(CompletableObserver s, Scheduler scheduler) { - this.s = s; + DisposeOnObserver(CompletableObserver observer, Scheduler scheduler) { + this.downstream = observer; this.scheduler = scheduler; } @@ -53,7 +53,7 @@ public void onComplete() { if (disposed) { return; } - s.onComplete(); + downstream.onComplete(); } @Override @@ -62,15 +62,15 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); return; } - s.onError(e); + downstream.onError(e); } @Override public void onSubscribe(final Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - s.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -87,8 +87,8 @@ public boolean isDisposed() { @Override public void run() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java index 4077270dfb..67e4ad5da1 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Action; @@ -25,10 +24,9 @@ /** * Execute an action after an onError, onComplete or a dispose event. - * - * @since 2.0.1 - experimental + * <p>History: 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class CompletableDoFinally extends Completable { final CompletableSource source; @@ -41,55 +39,55 @@ public CompletableDoFinally(CompletableSource source, Action onFinally) { } @Override - protected void subscribeActual(CompletableObserver s) { - source.subscribe(new DoFinallyObserver(s, onFinally)); + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new DoFinallyObserver(observer, onFinally)); } static final class DoFinallyObserver extends AtomicInteger implements CompletableObserver, Disposable { private static final long serialVersionUID = 4109457741734051389L; - final CompletableObserver actual; + final CompletableObserver downstream; final Action onFinally; - Disposable d; + Disposable upstream; DoFinallyObserver(CompletableObserver actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); runFinally(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); runFinally(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } void runFinally() { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java index 19d472af94..3499a553a1 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java @@ -31,8 +31,8 @@ public CompletableDoOnEvent(final CompletableSource source, final Consumer<? sup } @Override - protected void subscribeActual(final CompletableObserver s) { - source.subscribe(new DoOnEvent(s)); + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new DoOnEvent(observer)); } final class DoOnEvent implements CompletableObserver { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java index 13f16b4c3c..dc6b6c5fa2 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java @@ -23,7 +23,7 @@ private CompletableEmpty() { } @Override - public void subscribeActual(CompletableObserver s) { - EmptyDisposable.complete(s); + public void subscribeActual(CompletableObserver observer) { + EmptyDisposable.complete(observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java index 6d155da1b6..e7d6a23bf5 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java @@ -25,7 +25,7 @@ public CompletableError(Throwable error) { } @Override - protected void subscribeActual(CompletableObserver s) { - EmptyDisposable.error(error, s); + protected void subscribeActual(CompletableObserver observer) { + EmptyDisposable.error(error, observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java index d8dadfa005..16df486c42 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java @@ -29,7 +29,7 @@ public CompletableErrorSupplier(Callable<? extends Throwable> errorSupplier) { } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { Throwable error; try { @@ -39,7 +39,7 @@ protected void subscribeActual(CompletableObserver s) { error = e; } - EmptyDisposable.error(error, s); + EmptyDisposable.error(error, observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java index 1a4652840e..6722722390 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java @@ -17,6 +17,7 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Action; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromAction extends Completable { @@ -27,20 +28,22 @@ public CompletableFromAction(Action run) { } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { Disposable d = Disposables.empty(); - s.onSubscribe(d); + observer.onSubscribe(d); try { run.run(); } catch (Throwable e) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { - s.onError(e); + observer.onError(e); + } else { + RxJavaPlugins.onError(e); } return; } if (!d.isDisposed()) { - s.onComplete(); + observer.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java index 80bb1a0af0..6b7e68defb 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java @@ -18,6 +18,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromCallable extends Completable { @@ -28,20 +29,22 @@ public CompletableFromCallable(Callable<?> callable) { } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { Disposable d = Disposables.empty(); - s.onSubscribe(d); + observer.onSubscribe(d); try { callable.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { - s.onError(e); + observer.onError(e); + } else { + RxJavaPlugins.onError(e); } return; } if (!d.isDisposed()) { - s.onComplete(); + observer.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java index 509f173fa4..fdf3523b2f 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java @@ -25,8 +25,8 @@ public CompletableFromObservable(ObservableSource<T> observable) { } @Override - protected void subscribeActual(final CompletableObserver s) { - observable.subscribe(new CompletableFromObservableObserver<T>(s)); + protected void subscribeActual(final CompletableObserver observer) { + observable.subscribe(new CompletableFromObservableObserver<T>(observer)); } static final class CompletableFromObservableObserver<T> implements Observer<T> { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java index 4d0be70d35..ca33d582c9 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java @@ -28,32 +28,31 @@ public CompletableFromPublisher(Publisher<T> flowable) { } @Override - protected void subscribeActual(final CompletableObserver cs) { - flowable.subscribe(new FromPublisherSubscriber<T>(cs)); + protected void subscribeActual(final CompletableObserver downstream) { + flowable.subscribe(new FromPublisherSubscriber<T>(downstream)); } static final class FromPublisherSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final CompletableObserver cs; + final CompletableObserver downstream; - Subscription s; + Subscription upstream; - FromPublisherSubscriber(CompletableObserver actual) { - this.cs = actual; + FromPublisherSubscriber(CompletableObserver downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - cs.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } - @Override public void onNext(T t) { // ignored @@ -61,23 +60,23 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - cs.onError(t); + downstream.onError(t); } @Override public void onComplete() { - cs.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java index 789483ab8e..3ce78a167f 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java @@ -18,6 +18,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromRunnable extends Completable { @@ -28,20 +29,22 @@ public CompletableFromRunnable(Runnable runnable) { } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { Disposable d = Disposables.empty(); - s.onSubscribe(d); + observer.onSubscribe(d); try { runnable.run(); } catch (Throwable e) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { - s.onError(e); + observer.onError(e); + } else { + RxJavaPlugins.onError(e); } return; } if (!d.isDisposed()) { - s.onComplete(); + observer.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java index 1dab2b98d6..251ae5c8f3 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java @@ -25,8 +25,8 @@ public CompletableFromSingle(SingleSource<T> single) { } @Override - protected void subscribeActual(final CompletableObserver s) { - single.subscribe(new CompletableFromSingleObserver<T>(s)); + protected void subscribeActual(final CompletableObserver observer) { + single.subscribe(new CompletableFromSingleObserver<T>(observer)); } static final class CompletableFromSingleObserver<T> implements SingleObserver<T> { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java index a05e63cda7..0e1828dccb 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java @@ -37,42 +37,42 @@ protected void subscribeActual(CompletableObserver observer) { static final class HideCompletableObserver implements CompletableObserver, Disposable { - final CompletableObserver actual; + final CompletableObserver downstream; - Disposable d; + Disposable upstream; - HideCompletableObserver(CompletableObserver actual) { - this.actual = actual; + HideCompletableObserver(CompletableObserver downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java index ccc3616811..25a08d5821 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java @@ -29,11 +29,11 @@ public CompletableLift(CompletableSource source, CompletableOperator onLift) { } @Override - protected void subscribeActual(CompletableObserver s) { + protected void subscribeActual(CompletableObserver observer) { try { // TODO plugin wrapping - CompletableObserver sw = onLift.apply(s); + CompletableObserver sw = onLift.apply(observer); source.subscribe(sw); } catch (NullPointerException ex) { // NOPMD diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java new file mode 100644 index 0000000000..5eda7f6ae2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Completable source into a single Notification of + * equal kind. + * + * @param <T> the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class CompletableMaterialize<T> extends Single<Notification<T>> { + + final Completable source; + + public CompletableMaterialize(Completable source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver<? super Notification<T>> observer) { + source.subscribe(new MaterializeSingleObserver<T>(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java index fa59a499bd..a1dff5b342 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java @@ -36,8 +36,8 @@ public CompletableMerge(Publisher<? extends CompletableSource> source, int maxCo } @Override - public void subscribeActual(CompletableObserver s) { - CompletableMergeSubscriber parent = new CompletableMergeSubscriber(s, maxConcurrency, delayErrors); + public void subscribeActual(CompletableObserver observer) { + CompletableMergeSubscriber parent = new CompletableMergeSubscriber(observer, maxConcurrency, delayErrors); source.subscribe(parent); } @@ -47,7 +47,7 @@ static final class CompletableMergeSubscriber private static final long serialVersionUID = -2108443387387077490L; - final CompletableObserver actual; + final CompletableObserver downstream; final int maxConcurrency; final boolean delayErrors; @@ -55,10 +55,10 @@ static final class CompletableMergeSubscriber final CompositeDisposable set; - Subscription s; + Subscription upstream; CompletableMergeSubscriber(CompletableObserver actual, int maxConcurrency, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.maxConcurrency = maxConcurrency; this.delayErrors = delayErrors; this.set = new CompositeDisposable(); @@ -68,7 +68,7 @@ static final class CompletableMergeSubscriber @Override public void dispose() { - s.cancel(); + upstream.cancel(); set.dispose(); } @@ -79,9 +79,9 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); if (maxConcurrency == Integer.MAX_VALUE) { s.request(Long.MAX_VALUE); } else { @@ -106,7 +106,7 @@ public void onError(Throwable t) { if (error.addThrowable(t)) { if (getAndSet(0) > 0) { - actual.onError(error.terminate()); + downstream.onError(error.terminate()); } } else { RxJavaPlugins.onError(t); @@ -114,7 +114,7 @@ public void onError(Throwable t) { } else { if (error.addThrowable(t)) { if (decrementAndGet() == 0) { - actual.onError(error.terminate()); + downstream.onError(error.terminate()); } } else { RxJavaPlugins.onError(t); @@ -127,9 +127,9 @@ public void onComplete() { if (decrementAndGet() == 0) { Throwable ex = error.get(); if (ex != null) { - actual.onError(error.terminate()); + downstream.onError(error.terminate()); } else { - actual.onComplete(); + downstream.onComplete(); } } } @@ -137,12 +137,12 @@ public void onComplete() { void innerError(MergeInnerObserver inner, Throwable t) { set.delete(inner); if (!delayErrors) { - s.cancel(); + upstream.cancel(); set.dispose(); if (error.addThrowable(t)) { if (getAndSet(0) > 0) { - actual.onError(error.terminate()); + downstream.onError(error.terminate()); } } else { RxJavaPlugins.onError(t); @@ -150,10 +150,10 @@ void innerError(MergeInnerObserver inner, Throwable t) { } else { if (error.addThrowable(t)) { if (decrementAndGet() == 0) { - actual.onError(error.terminate()); + downstream.onError(error.terminate()); } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } else { @@ -167,13 +167,13 @@ void innerComplete(MergeInnerObserver inner) { if (decrementAndGet() == 0) { Throwable ex = error.get(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java index 783788c286..e6a42b105b 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java @@ -27,12 +27,12 @@ public CompletableMergeArray(CompletableSource[] sources) { } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { final CompositeDisposable set = new CompositeDisposable(); final AtomicBoolean once = new AtomicBoolean(); - InnerCompletableObserver shared = new InnerCompletableObserver(s, once, set, sources.length + 1); - s.onSubscribe(set); + InnerCompletableObserver shared = new InnerCompletableObserver(observer, once, set, sources.length + 1); + observer.onSubscribe(set); for (CompletableSource c : sources) { if (set.isDisposed()) { @@ -55,14 +55,14 @@ public void subscribeActual(final CompletableObserver s) { static final class InnerCompletableObserver extends AtomicInteger implements CompletableObserver { private static final long serialVersionUID = -8360547806504310570L; - final CompletableObserver actual; + final CompletableObserver downstream; final AtomicBoolean once; final CompositeDisposable set; InnerCompletableObserver(CompletableObserver actual, AtomicBoolean once, CompositeDisposable set, int n) { - this.actual = actual; + this.downstream = actual; this.once = once; this.set = set; this.lazySet(n); @@ -77,7 +77,7 @@ public void onSubscribe(Disposable d) { public void onError(Throwable e) { set.dispose(); if (once.compareAndSet(false, true)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -87,7 +87,7 @@ public void onError(Throwable e) { public void onComplete() { if (decrementAndGet() == 0) { if (once.compareAndSet(false, true)) { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java index c03939f655..c2508a46dd 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java @@ -29,13 +29,13 @@ public CompletableMergeDelayErrorArray(CompletableSource[] sources) { } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { final CompositeDisposable set = new CompositeDisposable(); final AtomicInteger wip = new AtomicInteger(sources.length + 1); final AtomicThrowable error = new AtomicThrowable(); - s.onSubscribe(set); + observer.onSubscribe(set); for (CompletableSource c : sources) { if (set.isDisposed()) { @@ -49,29 +49,29 @@ public void subscribeActual(final CompletableObserver s) { continue; } - c.subscribe(new MergeInnerCompletableObserver(s, set, error, wip)); + c.subscribe(new MergeInnerCompletableObserver(observer, set, error, wip)); } if (wip.decrementAndGet() == 0) { Throwable ex = error.terminate(); if (ex == null) { - s.onComplete(); + observer.onComplete(); } else { - s.onError(ex); + observer.onError(ex); } } } static final class MergeInnerCompletableObserver implements CompletableObserver { - final CompletableObserver actual; + final CompletableObserver downstream; final CompositeDisposable set; final AtomicThrowable error; final AtomicInteger wip; - MergeInnerCompletableObserver(CompletableObserver s, CompositeDisposable set, AtomicThrowable error, + MergeInnerCompletableObserver(CompletableObserver observer, CompositeDisposable set, AtomicThrowable error, AtomicInteger wip) { - this.actual = s; + this.downstream = observer; this.set = set; this.error = error; this.wip = wip; @@ -100,9 +100,9 @@ void tryTerminate() { if (wip.decrementAndGet() == 0) { Throwable ex = error.terminate(); if (ex == null) { - actual.onComplete(); + downstream.onComplete(); } else { - actual.onError(ex); + downstream.onError(ex); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java index 4593f3223e..40c84d1176 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java @@ -32,10 +32,10 @@ public CompletableMergeDelayErrorIterable(Iterable<? extends CompletableSource> } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + observer.onSubscribe(set); Iterator<? extends CompletableSource> iterator; @@ -43,7 +43,7 @@ public void subscribeActual(final CompletableObserver s) { iterator = ObjectHelper.requireNonNull(sources.iterator(), "The source iterator returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.onError(e); + observer.onError(e); return; } @@ -89,15 +89,15 @@ public void subscribeActual(final CompletableObserver s) { wip.getAndIncrement(); - c.subscribe(new MergeInnerCompletableObserver(s, set, error, wip)); + c.subscribe(new MergeInnerCompletableObserver(observer, set, error, wip)); } if (wip.decrementAndGet() == 0) { Throwable ex = error.terminate(); if (ex == null) { - s.onComplete(); + observer.onComplete(); } else { - s.onError(ex); + observer.onError(ex); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java index 32d015a0d4..0130250225 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java @@ -30,10 +30,10 @@ public CompletableMergeIterable(Iterable<? extends CompletableSource> sources) { } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + observer.onSubscribe(set); Iterator<? extends CompletableSource> iterator; @@ -41,13 +41,13 @@ public void subscribeActual(final CompletableObserver s) { iterator = ObjectHelper.requireNonNull(sources.iterator(), "The source iterator returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.onError(e); + observer.onError(e); return; } final AtomicInteger wip = new AtomicInteger(1); - MergeCompletableObserver shared = new MergeCompletableObserver(s, set, wip); + MergeCompletableObserver shared = new MergeCompletableObserver(observer, set, wip); for (;;) { if (set.isDisposed()) { return; @@ -100,12 +100,12 @@ static final class MergeCompletableObserver extends AtomicBoolean implements Com final CompositeDisposable set; - final CompletableObserver actual; + final CompletableObserver downstream; final AtomicInteger wip; MergeCompletableObserver(CompletableObserver actual, CompositeDisposable set, AtomicInteger wip) { - this.actual = actual; + this.downstream = actual; this.set = set; this.wip = wip; } @@ -119,7 +119,7 @@ public void onSubscribe(Disposable d) { public void onError(Throwable e) { set.dispose(); if (compareAndSet(false, true)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -129,7 +129,7 @@ public void onError(Throwable e) { public void onComplete() { if (wip.decrementAndGet() == 0) { if (compareAndSet(false, true)) { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java index 5874fda6be..73b52d54d2 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java @@ -23,8 +23,8 @@ private CompletableNever() { } @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(EmptyDisposable.NEVER); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(EmptyDisposable.NEVER); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java index 41f1cfb124..b931ed4a5d 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java @@ -30,25 +30,24 @@ public CompletableObserveOn(CompletableSource source, Scheduler scheduler) { } @Override - protected void subscribeActual(final CompletableObserver s) { - source.subscribe(new ObserveOnCompletableObserver(s, scheduler)); + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new ObserveOnCompletableObserver(observer, scheduler)); } static final class ObserveOnCompletableObserver extends AtomicReference<Disposable> implements CompletableObserver, Disposable, Runnable { - private static final long serialVersionUID = 8571289934935992137L; - final CompletableObserver actual; + final CompletableObserver downstream; final Scheduler scheduler; Throwable error; ObserveOnCompletableObserver(CompletableObserver actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @@ -65,7 +64,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -85,9 +84,9 @@ public void run() { Throwable ex = error; if (ex != null) { error = null; - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java index d4de7495fa..5ff0cc3235 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java @@ -30,22 +30,22 @@ public CompletableOnErrorComplete(CompletableSource source, Predicate<? super Th } @Override - protected void subscribeActual(final CompletableObserver s) { + protected void subscribeActual(final CompletableObserver observer) { - source.subscribe(new OnError(s)); + source.subscribe(new OnError(observer)); } final class OnError implements CompletableObserver { - private final CompletableObserver s; + private final CompletableObserver downstream; - OnError(CompletableObserver s) { - this.s = s; + OnError(CompletableObserver observer) { + this.downstream = observer; } @Override public void onComplete() { - s.onComplete(); + downstream.onComplete(); } @Override @@ -56,20 +56,20 @@ public void onError(Throwable e) { b = predicate.test(e); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } if (b) { - s.onComplete(); + downstream.onComplete(); } else { - s.onError(e); + downstream.onError(e); } } @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + downstream.onSubscribe(d); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java index 97a4c1b1cb..02180e4e91 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java @@ -46,22 +46,21 @@ public CompletablePeek(CompletableSource source, Consumer<? super Disposable> on } @Override - protected void subscribeActual(final CompletableObserver s) { + protected void subscribeActual(final CompletableObserver observer) { - source.subscribe(new CompletableObserverImplementation(s)); + source.subscribe(new CompletableObserverImplementation(observer)); } final class CompletableObserverImplementation implements CompletableObserver, Disposable { - final CompletableObserver actual; + final CompletableObserver downstream; - Disposable d; + Disposable upstream; - CompletableObserverImplementation(CompletableObserver actual) { - this.actual = actual; + CompletableObserverImplementation(CompletableObserver downstream) { + this.downstream = downstream; } - @Override public void onSubscribe(final Disposable d) { try { @@ -69,19 +68,19 @@ public void onSubscribe(final Disposable d) { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); d.dispose(); - this.d = DisposableHelper.DISPOSED; - EmptyDisposable.error(ex, actual); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(ex, downstream); return; } - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onError(Throwable e) { - if (d == DisposableHelper.DISPOSED) { + if (upstream == DisposableHelper.DISPOSED) { RxJavaPlugins.onError(e); return; } @@ -93,14 +92,14 @@ public void onError(Throwable e) { e = new CompositeException(e, ex); } - actual.onError(e); + downstream.onError(e); doAfter(); } @Override public void onComplete() { - if (d == DisposableHelper.DISPOSED) { + if (upstream == DisposableHelper.DISPOSED) { return; } @@ -109,11 +108,11 @@ public void onComplete() { onTerminate.run(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } - actual.onComplete(); + downstream.onComplete(); doAfter(); } @@ -135,12 +134,12 @@ public void dispose() { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(e); } - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java index 8817843e13..5111d46f32 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java @@ -13,11 +13,14 @@ package io.reactivex.internal.operators.completable; +import java.util.concurrent.atomic.AtomicReference; + import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; -import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; public final class CompletableResumeNext extends Completable { @@ -31,75 +34,69 @@ public CompletableResumeNext(CompletableSource source, this.errorMapper = errorMapper; } + @Override + protected void subscribeActual(final CompletableObserver observer) { + ResumeNextObserver parent = new ResumeNextObserver(observer, errorMapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class ResumeNextObserver + extends AtomicReference<Disposable> + implements CompletableObserver, Disposable { + private static final long serialVersionUID = 5018523762564524046L; - @Override - protected void subscribeActual(final CompletableObserver s) { + final CompletableObserver downstream; - final SequentialDisposable sd = new SequentialDisposable(); - s.onSubscribe(sd); - source.subscribe(new ResumeNext(s, sd)); - } + final Function<? super Throwable, ? extends CompletableSource> errorMapper; - final class ResumeNext implements CompletableObserver { + boolean once; - final CompletableObserver s; - final SequentialDisposable sd; + ResumeNextObserver(CompletableObserver observer, Function<? super Throwable, ? extends CompletableSource> errorMapper) { + this.downstream = observer; + this.errorMapper = errorMapper; + } - ResumeNext(CompletableObserver s, SequentialDisposable sd) { - this.s = s; - this.sd = sd; + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); } @Override public void onComplete() { - s.onComplete(); + downstream.onComplete(); } @Override public void onError(Throwable e) { + if (once) { + downstream.onError(e); + return; + } + once = true; + CompletableSource c; try { - c = errorMapper.apply(e); + c = ObjectHelper.requireNonNull(errorMapper.apply(e), "The errorMapper returned a null CompletableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(new CompositeException(ex, e)); + downstream.onError(new CompositeException(e, ex)); return; } - if (c == null) { - NullPointerException npe = new NullPointerException("The CompletableConsumable returned is null"); - npe.initCause(e); - s.onError(npe); - return; - } - - c.subscribe(new OnErrorObserver()); + c.subscribe(this); } @Override - public void onSubscribe(Disposable d) { - sd.update(d); + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); } - final class OnErrorObserver implements CompletableObserver { - - @Override - public void onComplete() { - s.onComplete(); - } - - @Override - public void onError(Throwable e) { - s.onError(e); - } - - @Override - public void onSubscribe(Disposable d) { - sd.update(d); - } - + @Override + public void dispose() { + DisposableHelper.dispose(this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java index b749d6f529..3b2b394694 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java @@ -30,10 +30,10 @@ public CompletableSubscribeOn(CompletableSource source, Scheduler scheduler) { } @Override - protected void subscribeActual(final CompletableObserver s) { + protected void subscribeActual(final CompletableObserver observer) { - final SubscribeOnObserver parent = new SubscribeOnObserver(s, source); - s.onSubscribe(parent); + final SubscribeOnObserver parent = new SubscribeOnObserver(observer, source); + observer.onSubscribe(parent); Disposable f = scheduler.scheduleDirect(parent); @@ -47,14 +47,14 @@ static final class SubscribeOnObserver private static final long serialVersionUID = 7000911171163930287L; - final CompletableObserver actual; + final CompletableObserver downstream; final SequentialDisposable task; final CompletableSource source; SubscribeOnObserver(CompletableObserver actual, CompletableSource source) { - this.actual = actual; + this.downstream = actual; this.source = source; this.task = new SequentialDisposable(); } @@ -71,12 +71,12 @@ public void onSubscribe(Disposable d) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java new file mode 100644 index 0000000000..7a27b68f9b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Terminates the sequence if either the main or the other Completable terminate. + * <p>History: 2.1.17 - experimental + * @since 2.2 + */ +public final class CompletableTakeUntilCompletable extends Completable { + + final Completable source; + + final CompletableSource other; + + public CompletableTakeUntilCompletable(Completable source, + CompletableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + TakeUntilMainObserver parent = new TakeUntilMainObserver(observer); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + source.subscribe(parent); + } + + static final class TakeUntilMainObserver extends AtomicReference<Disposable> + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = 3533011714830024923L; + + final CompletableObserver downstream; + + final OtherObserver other; + + final AtomicBoolean once; + + TakeUntilMainObserver(CompletableObserver downstream) { + this.downstream = downstream; + this.other = new OtherObserver(this); + this.once = new AtomicBoolean(); + } + + @Override + public void dispose() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + DisposableHelper.dispose(other); + } + } + + @Override + public boolean isDisposed() { + return once.get(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(other); + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(other); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + downstream.onComplete(); + } + } + + void innerError(Throwable e) { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + static final class OtherObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = 5176264485428790318L; + final TakeUntilMainObserver parent; + + OtherObserver(TakeUntilMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java index 9788e1cf1c..c11daa7342 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java @@ -20,6 +20,8 @@ import io.reactivex.disposables.*; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class CompletableTimeout extends Completable { final CompletableSource source; @@ -38,29 +40,29 @@ public CompletableTimeout(CompletableSource source, long timeout, } @Override - public void subscribeActual(final CompletableObserver s) { + public void subscribeActual(final CompletableObserver observer) { final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + observer.onSubscribe(set); final AtomicBoolean once = new AtomicBoolean(); - Disposable timer = scheduler.scheduleDirect(new DisposeTask(once, set, s), timeout, unit); + Disposable timer = scheduler.scheduleDirect(new DisposeTask(once, set, observer), timeout, unit); set.add(timer); - source.subscribe(new TimeOutObserver(set, once, s)); + source.subscribe(new TimeOutObserver(set, once, observer)); } static final class TimeOutObserver implements CompletableObserver { private final CompositeDisposable set; private final AtomicBoolean once; - private final CompletableObserver s; + private final CompletableObserver downstream; - TimeOutObserver(CompositeDisposable set, AtomicBoolean once, CompletableObserver s) { + TimeOutObserver(CompositeDisposable set, AtomicBoolean once, CompletableObserver observer) { this.set = set; this.once = once; - this.s = s; + this.downstream = observer; } @Override @@ -72,7 +74,7 @@ public void onSubscribe(Disposable d) { public void onError(Throwable e) { if (once.compareAndSet(false, true)) { set.dispose(); - s.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -82,7 +84,7 @@ public void onError(Throwable e) { public void onComplete() { if (once.compareAndSet(false, true)) { set.dispose(); - s.onComplete(); + downstream.onComplete(); } } @@ -91,12 +93,12 @@ public void onComplete() { final class DisposeTask implements Runnable { private final AtomicBoolean once; final CompositeDisposable set; - final CompletableObserver s; + final CompletableObserver downstream; - DisposeTask(AtomicBoolean once, CompositeDisposable set, CompletableObserver s) { + DisposeTask(AtomicBoolean once, CompositeDisposable set, CompletableObserver observer) { this.once = once; this.set = set; - this.s = s; + this.downstream = observer; } @Override @@ -104,7 +106,7 @@ public void run() { if (once.compareAndSet(false, true)) { set.clear(); if (other == null) { - s.onError(new TimeoutException()); + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); } else { other.subscribe(new DisposeObserver()); } @@ -121,13 +123,13 @@ public void onSubscribe(Disposable d) { @Override public void onError(Throwable e) { set.dispose(); - s.onError(e); + downstream.onError(e); } @Override public void onComplete() { set.dispose(); - s.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java index 2435365814..4f1eae40c4 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java @@ -36,24 +36,24 @@ public CompletableTimer(long delay, TimeUnit unit, Scheduler scheduler) { } @Override - protected void subscribeActual(final CompletableObserver s) { - TimerDisposable parent = new TimerDisposable(s); - s.onSubscribe(parent); + protected void subscribeActual(final CompletableObserver observer) { + TimerDisposable parent = new TimerDisposable(observer); + observer.onSubscribe(parent); parent.setFuture(scheduler.scheduleDirect(parent, delay, unit)); } static final class TimerDisposable extends AtomicReference<Disposable> implements Disposable, Runnable { private static final long serialVersionUID = 3167244060586201109L; - final CompletableObserver actual; + final CompletableObserver downstream; - TimerDisposable(final CompletableObserver actual) { - this.actual = actual; + TimerDisposable(final CompletableObserver downstream) { + this.downstream = downstream; } @Override public void run() { - actual.onComplete(); + downstream.onComplete(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java index 9a0a555b84..f55a310b0a 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java @@ -15,6 +15,8 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.BasicQueueDisposable; /** * Wraps a Completable and exposes it as an Observable. @@ -34,8 +36,12 @@ protected void subscribeActual(Observer<? super T> observer) { source.subscribe(new ObserverCompletableObserver(observer)); } - static final class ObserverCompletableObserver implements CompletableObserver { - private final Observer<?> observer; + static final class ObserverCompletableObserver extends BasicQueueDisposable<Void> + implements CompletableObserver { + + final Observer<?> observer; + + Disposable upstream; ObserverCompletableObserver(Observer<?> observer) { this.observer = observer; @@ -53,7 +59,40 @@ public void onError(Throwable e) { @Override public void onSubscribe(Disposable d) { - observer.onSubscribe(d); + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + observer.onSubscribe(this); + } + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + + @Override + public Void poll() throws Exception { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + // always empty + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java index bac12bdbd1..e0e5559723 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java @@ -34,8 +34,8 @@ public CompletableToSingle(CompletableSource source, } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - source.subscribe(new ToSingle(s)); + protected void subscribeActual(final SingleObserver<? super T> observer) { + source.subscribe(new ToSingle(observer)); } final class ToSingle implements CompletableObserver { diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java index 88f8a57516..1f107f2c00 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java @@ -40,7 +40,6 @@ public CompletableUsing(Callable<R> resourceSupplier, this.eager = eager; } - @Override protected void subscribeActual(CompletableObserver observer) { R resource; @@ -89,28 +88,27 @@ static final class UsingObserver<R> extends AtomicReference<Object> implements CompletableObserver, Disposable { - private static final long serialVersionUID = -674404550052917487L; - final CompletableObserver actual; + final CompletableObserver downstream; final Consumer<? super R> disposer; final boolean eager; - Disposable d; + Disposable upstream; UsingObserver(CompletableObserver actual, R resource, Consumer<? super R> disposer, boolean eager) { super(resource); - this.actual = actual; + this.downstream = actual; this.disposer = disposer; this.eager = eager; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; disposeResourceAfter(); } @@ -129,22 +127,22 @@ void disposeResourceAfter() { @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object resource = getAndSet(this); if (resource != this) { @@ -159,7 +157,7 @@ public void onError(Throwable e) { } } - actual.onError(e); + downstream.onError(e); if (!eager) { disposeResourceAfter(); @@ -169,7 +167,7 @@ public void onError(Throwable e) { @SuppressWarnings("unchecked") @Override public void onComplete() { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object resource = getAndSet(this); if (resource != this) { @@ -177,7 +175,7 @@ public void onComplete() { disposer.accept((R)resource); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } } else { @@ -185,7 +183,7 @@ public void onComplete() { } } - actual.onComplete(); + downstream.onComplete(); if (!eager) { disposeResourceAfter(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java index 478aa2281e..d09af33ee0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java @@ -62,7 +62,7 @@ static final class BlockingFlowableIterator<T> long produced; volatile boolean done; - Throwable error; + volatile Throwable error; BlockingFlowableIterator(int batchSize) { this.queue = new SpscArrayQueue<T>(batchSize); @@ -75,6 +75,13 @@ static final class BlockingFlowableIterator<T> @Override public boolean hasNext() { for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } boolean d = done; boolean empty = queue.isEmpty(); if (d) { @@ -90,7 +97,7 @@ public boolean hasNext() { BlockingHelper.verifyNonBlocking(); lock.lock(); try { - while (!done && queue.isEmpty()) { + while (!done && queue.isEmpty() && !isDisposed()) { condition.await(); } } catch (InterruptedException ex) { @@ -125,9 +132,7 @@ public T next() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(batchSize); - } + SubscriptionHelper.setOnce(this, s, batchSize); } @Override @@ -177,11 +182,12 @@ public void remove() { @Override public void dispose() { SubscriptionHelper.cancel(this); + signalConsumer(); } @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java index 298a3f21be..235d8507dc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java @@ -42,10 +42,6 @@ public BlockingFlowableMostRecent(Flowable<T> source, T initialValue) { public Iterator<T> iterator() { MostRecentSubscriber<T> mostRecentSubscriber = new MostRecentSubscriber<T>(initialValue); - /** - * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain - * since it is for BlockingObservable. - */ source.subscribe(mostRecentSubscriber); return mostRecentSubscriber.getIterable(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java index 9fd3c09d00..8cd40288e4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java @@ -48,7 +48,7 @@ public Iterator<T> iterator() { // test needs to access the observer.waiting flag static final class NextIterator<T> implements Iterator<T> { - private final NextSubscriber<T> observer; + private final NextSubscriber<T> subscriber; private final Publisher<? extends T> items; private T next; private boolean hasNext = true; @@ -56,9 +56,9 @@ static final class NextIterator<T> implements Iterator<T> { private Throwable error; private boolean started; - NextIterator(Publisher<? extends T> items, NextSubscriber<T> observer) { + NextIterator(Publisher<? extends T> items, NextSubscriber<T> subscriber) { this.items = items; - this.observer = observer; + this.subscriber = subscriber; } @Override @@ -82,12 +82,12 @@ private boolean moveToNext() { if (!started) { started = true; // if not started, start now - observer.setWaiting(); + subscriber.setWaiting(); Flowable.<T>fromPublisher(items) - .materialize().subscribe(observer); + .materialize().subscribe(subscriber); } - Notification<T> nextNotification = observer.takeNext(); + Notification<T> nextNotification = subscriber.takeNext(); if (nextNotification.isOnNext()) { isNextConsumed = false; next = nextNotification.getValue(); @@ -105,7 +105,7 @@ private boolean moveToNext() { } throw new IllegalStateException("Should not reach here"); } catch (InterruptedException e) { - observer.dispose(); + subscriber.dispose(); error = e; throw ExceptionHelper.wrapOrThrow(e); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java index 6d7faa61dd..608089ace4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java @@ -39,7 +39,7 @@ static final class AllSubscriber<T> extends DeferredScalarSubscription<Boolean> private static final long serialVersionUID = -3521127104134758517L; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; @@ -47,11 +47,12 @@ static final class AllSubscriber<T> extends DeferredScalarSubscription<Boolean> super(actual); this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -66,13 +67,13 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } if (!b) { done = true; - s.cancel(); + upstream.cancel(); complete(false); } } @@ -84,7 +85,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -100,7 +101,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java index 9812961382..1ec3e9460d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java @@ -34,8 +34,8 @@ public FlowableAllSingle(Flowable<T> source, Predicate<? super T> predicate) { } @Override - protected void subscribeActual(SingleObserver<? super Boolean> s) { - source.subscribe(new AllSubscriber<T>(s, predicate)); + protected void subscribeActual(SingleObserver<? super Boolean> observer) { + source.subscribe(new AllSubscriber<T>(observer, predicate)); } @Override @@ -45,23 +45,24 @@ public Flowable<Boolean> fuseToFlowable() { static final class AllSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; AllSubscriber(SingleObserver<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -76,16 +77,16 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; onError(e); return; } if (!b) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(false); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(false); } } @@ -96,8 +97,8 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override @@ -106,20 +107,20 @@ public void onComplete() { return; } done = true; - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; - actual.onSuccess(true); + downstream.onSuccess(true); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java index 1e14453afa..203c62f999 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java @@ -74,14 +74,14 @@ public void subscribeActual(Subscriber<? super T> s) { } static final class AmbCoordinator<T> implements Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AmbInnerSubscriber<T>[] subscribers; final AtomicInteger winner = new AtomicInteger(); @SuppressWarnings("unchecked") AmbCoordinator(Subscriber<? super T> actual, int count) { - this.actual = actual; + this.downstream = actual; this.subscribers = new AmbInnerSubscriber[count]; } @@ -89,10 +89,10 @@ public void subscribe(Publisher<? extends T>[] sources) { AmbInnerSubscriber<T>[] as = subscribers; int len = as.length; for (int i = 0; i < len; i++) { - as[i] = new AmbInnerSubscriber<T>(this, i + 1, actual); + as[i] = new AmbInnerSubscriber<T>(this, i + 1, downstream); } winner.lazySet(0); // release the contents of 'as' - actual.onSubscribe(this); + downstream.onSubscribe(this); for (int i = 0; i < len; i++) { if (winner.get() != 0) { @@ -152,16 +152,16 @@ static final class AmbInnerSubscriber<T> extends AtomicReference<Subscription> i private static final long serialVersionUID = -1185974347409665484L; final AmbCoordinator<T> parent; final int index; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; boolean won; final AtomicLong missedRequested = new AtomicLong(); - AmbInnerSubscriber(AmbCoordinator<T> parent, int index, Subscriber<? super T> actual) { + AmbInnerSubscriber(AmbCoordinator<T> parent, int index, Subscriber<? super T> downstream) { this.parent = parent; this.index = index; - this.actual = actual; + this.downstream = downstream; } @Override @@ -177,11 +177,11 @@ public void request(long n) { @Override public void onNext(T t) { if (won) { - actual.onNext(t); + downstream.onNext(t); } else { if (parent.win(index)) { won = true; - actual.onNext(t); + downstream.onNext(t); } else { get().cancel(); } @@ -191,11 +191,11 @@ public void onNext(T t) { @Override public void onError(Throwable t) { if (won) { - actual.onError(t); + downstream.onError(t); } else { if (parent.win(index)) { won = true; - actual.onError(t); + downstream.onError(t); } else { get().cancel(); RxJavaPlugins.onError(t); @@ -206,11 +206,11 @@ public void onError(Throwable t) { @Override public void onComplete() { if (won) { - actual.onComplete(); + downstream.onComplete(); } else { if (parent.win(index)) { won = true; - actual.onComplete(); + downstream.onComplete(); } else { get().cancel(); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java index 39ed01534a..aed50107e4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java @@ -38,7 +38,7 @@ static final class AnySubscriber<T> extends DeferredScalarSubscription<Boolean> final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; @@ -46,11 +46,12 @@ static final class AnySubscriber<T> extends DeferredScalarSubscription<Boolean> super(actual); this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -65,13 +66,13 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } if (b) { done = true; - s.cancel(); + upstream.cancel(); complete(true); } } @@ -84,7 +85,7 @@ public void onError(Throwable t) { } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -98,7 +99,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java index 94c0ab9a13..0c30c11107 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java @@ -33,8 +33,8 @@ public FlowableAnySingle(Flowable<T> source, Predicate<? super T> predicate) { } @Override - protected void subscribeActual(SingleObserver<? super Boolean> s) { - source.subscribe(new AnySubscriber<T>(s, predicate)); + protected void subscribeActual(SingleObserver<? super Boolean> observer) { + source.subscribe(new AnySubscriber<T>(observer, predicate)); } @Override @@ -44,23 +44,24 @@ public Flowable<Boolean> fuseToFlowable() { static final class AnySubscriber<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; AnySubscriber(SingleObserver<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -75,16 +76,16 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; onError(e); return; } if (b) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(true); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(true); } } @@ -96,28 +97,28 @@ public void onError(Throwable t) { } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(false); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(false); } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java index 65cd6bb6b5..619d551588 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java @@ -23,8 +23,8 @@ import io.reactivex.functions.Consumer; /** - * Wraps a ConnectableObservable and calls its connect() method once - * the specified number of Subscribers have subscribed. + * Wraps a {@link ConnectableFlowable} and calls its {@code connect()} method once + * the specified number of {@link Subscriber}s have subscribed. * * @param <T> the value type of the chain */ diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java index d79c6f3061..c5ac6884f6 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java @@ -63,7 +63,7 @@ public static <T> void subscribe(Publisher<? extends T> o, Subscriber<? super T> if (bs.isCancelled()) { break; } - if (o == BlockingSubscriber.TERMINATED + if (v == BlockingSubscriber.TERMINATED || NotificationLite.acceptFull(v, subscriber)) { break; } @@ -108,4 +108,23 @@ public static <T> void subscribe(Publisher<? extends T> o, final Consumer<? supe ObjectHelper.requireNonNull(onComplete, "onComplete is null"); subscribe(o, new LambdaSubscriber<T>(onNext, onError, onComplete, Functions.REQUEST_MAX)); } + + /** + * Subscribes to the source and calls the given actions on the current thread. + * @param o the source publisher + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param bufferSize the number of elements to prefetch from the source Publisher + * @param <T> the value type + */ + public static <T> void subscribe(Publisher<? extends T> o, final Consumer<? super T> onNext, + final Consumer<? super Throwable> onError, final Action onComplete, int bufferSize) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.verifyPositive(bufferSize, "number > 0 required"); + subscribe(o, new BoundedSubscriber<T>(onNext, onError, onComplete, Functions.boundedConsumer(bufferSize), + bufferSize)); + } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java index 87b0d7331e..f2f940ac2e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java @@ -55,7 +55,7 @@ public void subscribeActual(Subscriber<? super C> s) { static final class PublisherBufferExactSubscriber<T, C extends Collection<? super T>> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super C> actual; + final Subscriber<? super C> downstream; final Callable<C> bufferSupplier; @@ -63,14 +63,14 @@ static final class PublisherBufferExactSubscriber<T, C extends Collection<? supe C buffer; - Subscription s; + Subscription upstream; boolean done; int index; PublisherBufferExactSubscriber(Subscriber<? super C> actual, int size, Callable<C> bufferSupplier) { - this.actual = actual; + this.downstream = actual; this.size = size; this.bufferSupplier = bufferSupplier; } @@ -78,21 +78,21 @@ static final class PublisherBufferExactSubscriber<T, C extends Collection<? supe @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { - s.request(BackpressureHelper.multiplyCap(n, size)); + upstream.request(BackpressureHelper.multiplyCap(n, size)); } } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -123,7 +123,7 @@ public void onNext(T t) { if (i == size) { index = 0; buffer = null; - actual.onNext(b); + downstream.onNext(b); } else { index = i; } @@ -136,7 +136,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -149,9 +149,9 @@ public void onComplete() { C b = buffer; if (b != null && !b.isEmpty()) { - actual.onNext(b); + downstream.onNext(b); } - actual.onComplete(); + downstream.onComplete(); } } @@ -159,10 +159,9 @@ static final class PublisherBufferSkipSubscriber<T, C extends Collection<? super extends AtomicInteger implements FlowableSubscriber<T>, Subscription { - private static final long serialVersionUID = -5616169793639412593L; - final Subscriber<? super C> actual; + final Subscriber<? super C> downstream; final Callable<C> bufferSupplier; @@ -172,7 +171,7 @@ static final class PublisherBufferSkipSubscriber<T, C extends Collection<? super C buffer; - Subscription s; + Subscription upstream; boolean done; @@ -180,7 +179,7 @@ static final class PublisherBufferSkipSubscriber<T, C extends Collection<? super PublisherBufferSkipSubscriber(Subscriber<? super C> actual, int size, int skip, Callable<C> bufferSupplier) { - this.actual = actual; + this.downstream = actual; this.size = size; this.skip = skip; this.bufferSupplier = bufferSupplier; @@ -195,25 +194,25 @@ public void request(long n) { // + (n - 1) gaps long v = BackpressureHelper.multiplyCap(skip - size, n - 1); - s.request(BackpressureHelper.addCap(u, v)); + upstream.request(BackpressureHelper.addCap(u, v)); } else { // n full buffer + gap - s.request(BackpressureHelper.multiplyCap(skip, n)); + upstream.request(BackpressureHelper.multiplyCap(skip, n)); } } } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -245,7 +244,7 @@ public void onNext(T t) { b.add(t); if (b.size() == size) { buffer = null; - actual.onNext(b); + downstream.onNext(b); } } @@ -265,7 +264,7 @@ public void onError(Throwable t) { done = true; buffer = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -279,21 +278,20 @@ public void onComplete() { buffer = null; if (b != null) { - actual.onNext(b); + downstream.onNext(b); } - actual.onComplete(); + downstream.onComplete(); } } - static final class PublisherBufferOverlappingSubscriber<T, C extends Collection<? super T>> extends AtomicLong implements FlowableSubscriber<T>, Subscription, BooleanSupplier { private static final long serialVersionUID = -7370244972039324525L; - final Subscriber<? super C> actual; + final Subscriber<? super C> downstream; final Callable<C> bufferSupplier; @@ -305,7 +303,7 @@ static final class PublisherBufferOverlappingSubscriber<T, C extends Collection< final AtomicBoolean once; - Subscription s; + Subscription upstream; boolean done; @@ -317,7 +315,7 @@ static final class PublisherBufferOverlappingSubscriber<T, C extends Collection< PublisherBufferOverlappingSubscriber(Subscriber<? super C> actual, int size, int skip, Callable<C> bufferSupplier) { - this.actual = actual; + this.downstream = actual; this.size = size; this.skip = skip; this.bufferSupplier = bufferSupplier; @@ -333,7 +331,7 @@ public boolean getAsBoolean() { @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { - if (QueueDrainHelper.postCompleteRequest(n, actual, buffers, this, this)) { + if (QueueDrainHelper.postCompleteRequest(n, downstream, buffers, this, this)) { return; } @@ -343,11 +341,11 @@ public void request(long n) { // + 1 full buffer long r = BackpressureHelper.addCap(size, u); - s.request(r); + upstream.request(r); } else { // n skips long r = BackpressureHelper.multiplyCap(skip, n); - s.request(r); + upstream.request(r); } } } @@ -355,15 +353,15 @@ public void request(long n) { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -401,7 +399,7 @@ public void onNext(T t) { produced++; - actual.onNext(b); + downstream.onNext(b); } for (C b0 : bs) { @@ -424,7 +422,7 @@ public void onError(Throwable t) { done = true; buffers.clear(); - actual.onError(t); + downstream.onError(t); } @Override @@ -439,7 +437,7 @@ public void onComplete() { if (p != 0L) { BackpressureHelper.produced(this, p); } - QueueDrainHelper.postComplete(actual, buffers, this, this); + QueueDrainHelper.postComplete(downstream, buffers, this, this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java index e375d5f720..44146a5829 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java @@ -15,22 +15,19 @@ import java.util.*; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.fuseable.SimplePlainQueue; -import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; import io.reactivex.internal.subscriptions.SubscriptionHelper; -import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subscribers.*; public final class FlowableBufferBoundary<T, U extends Collection<? super T>, Open, Close> extends AbstractFlowableWithUpstream<T, U> { @@ -48,48 +45,72 @@ public FlowableBufferBoundary(Flowable<T> source, Publisher<? extends Open> buff @Override protected void subscribeActual(Subscriber<? super U> s) { - source.subscribe(new BufferBoundarySubscriber<T, U, Open, Close>( - new SerializedSubscriber<U>(s), - bufferOpen, bufferClose, bufferSupplier - )); + BufferBoundarySubscriber<T, U, Open, Close> parent = + new BufferBoundarySubscriber<T, U, Open, Close>( + s, bufferOpen, bufferClose, bufferSupplier + ); + s.onSubscribe(parent); + source.subscribe(parent); } - static final class BufferBoundarySubscriber<T, U extends Collection<? super T>, Open, Close> - extends QueueDrainSubscriber<T, U, U> implements Subscription, Disposable { + static final class BufferBoundarySubscriber<T, C extends Collection<? super T>, Open, Close> + extends AtomicInteger implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -8466418554264089604L; + + final Subscriber<? super C> downstream; + + final Callable<C> bufferSupplier; + final Publisher<? extends Open> bufferOpen; + final Function<? super Open, ? extends Publisher<? extends Close>> bufferClose; - final Callable<U> bufferSupplier; - final CompositeDisposable resources; - Subscription s; + final CompositeDisposable subscribers; + + final AtomicLong requested; + + final AtomicReference<Subscription> upstream; - final List<U> buffers; + final AtomicThrowable errors; - final AtomicInteger windows = new AtomicInteger(); + volatile boolean done; - BufferBoundarySubscriber(Subscriber<? super U> actual, + final SpscLinkedArrayQueue<C> queue; + + volatile boolean cancelled; + + long index; + + Map<Long, C> buffers; + + long emitted; + + BufferBoundarySubscriber(Subscriber<? super C> actual, Publisher<? extends Open> bufferOpen, Function<? super Open, ? extends Publisher<? extends Close>> bufferClose, - Callable<U> bufferSupplier) { - super(actual, new MpscLinkedQueue<U>()); + Callable<C> bufferSupplier + ) { + this.downstream = actual; + this.bufferSupplier = bufferSupplier; this.bufferOpen = bufferOpen; this.bufferClose = bufferClose; - this.bufferSupplier = bufferSupplier; - this.buffers = new LinkedList<U>(); - this.resources = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue<C>(bufferSize()); + this.subscribers = new CompositeDisposable(); + this.requested = new AtomicLong(); + this.upstream = new AtomicReference<Subscription>(); + this.buffers = new LinkedHashMap<Long, C>(); + this.errors = new AtomicThrowable(); } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - - BufferOpenSubscriber<T, U, Open, Close> bos = new BufferOpenSubscriber<T, U, Open, Close>(this); - resources.add(bos); + if (SubscriptionHelper.setOnce(this.upstream, s)) { - actual.onSubscribe(this); + BufferOpenSubscriber<Open> open = new BufferOpenSubscriber<Open>(this); + subscribers.add(open); - windows.lazySet(1); - bufferOpen.subscribe(bos); + bufferOpen.subscribe(open); s.request(Long.MAX_VALUE); } @@ -98,7 +119,11 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { synchronized (this) { - for (U b : buffers) { + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { b.add(t); } } @@ -106,206 +131,290 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - cancel(); - cancelled = true; - synchronized (this) { - buffers.clear(); + if (errors.addThrowable(t)) { + subscribers.dispose(); + synchronized (this) { + buffers = null; + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); } - actual.onError(t); } @Override public void onComplete() { - if (windows.decrementAndGet() == 0) { - complete(); - } - } - - void complete() { - List<U> list; + subscribers.dispose(); synchronized (this) { - list = new ArrayList<U>(buffers); - buffers.clear(); - } - - SimplePlainQueue<U> q = queue; - for (U u : list) { - q.offer(u); + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + queue.offer(b); + } + buffers = null; } done = true; - if (enter()) { - QueueDrainHelper.drainMaxLoop(q, actual, false, this, this); - } + drain(); } @Override public void request(long n) { - requested(n); - } - - @Override - public void dispose() { - resources.dispose(); - } - - @Override - public boolean isDisposed() { - return resources.isDisposed(); + BackpressureHelper.add(requested, n); + drain(); } @Override public void cancel() { - if (!cancelled) { + if (SubscriptionHelper.cancel(upstream)) { cancelled = true; - dispose(); + subscribers.dispose(); + synchronized (this) { + buffers = null; + } + if (getAndIncrement() != 0) { + queue.clear(); + } } } - @Override - public boolean accept(Subscriber<? super U> a, U v) { - a.onNext(v); - return true; - } - - void open(Open window) { - if (cancelled) { + void open(Open token) { + Publisher<? extends Close> p; + C buf; + try { + buf = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null Collection"); + p = ObjectHelper.requireNonNull(bufferClose.apply(token), "The bufferClose returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + SubscriptionHelper.cancel(upstream); + onError(ex); return; } - U b; - - try { - b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - onError(e); - return; + long idx = index; + index = idx + 1; + synchronized (this) { + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + bufs.put(idx, buf); } - Publisher<? extends Close> p; + BufferCloseSubscriber<T, C> bc = new BufferCloseSubscriber<T, C>(this, idx); + subscribers.add(bc); + p.subscribe(bc); + } - try { - p = ObjectHelper.requireNonNull(bufferClose.apply(window), "The buffer closing publisher is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - onError(e); - return; + void openComplete(BufferOpenSubscriber<Open> os) { + subscribers.delete(os); + if (subscribers.size() == 0) { + SubscriptionHelper.cancel(upstream); + done = true; + drain(); } + } - if (cancelled) { - return; + void close(BufferCloseSubscriber<T, C> closer, long idx) { + subscribers.delete(closer); + boolean makeDone = false; + if (subscribers.size() == 0) { + makeDone = true; + SubscriptionHelper.cancel(upstream); } - synchronized (this) { - if (cancelled) { + Map<Long, C> bufs = buffers; + if (bufs == null) { return; } - buffers.add(b); + queue.offer(buffers.remove(idx)); } + if (makeDone) { + done = true; + } + drain(); + } + + void boundaryError(Disposable subscriber, Throwable ex) { + SubscriptionHelper.cancel(upstream); + subscribers.delete(subscriber); + onError(ex); + } - BufferCloseSubscriber<T, U, Open, Close> bcs = new BufferCloseSubscriber<T, U, Open, Close>(b, this); - resources.add(bcs); + void drain() { + if (getAndIncrement() != 0) { + return; + } - windows.getAndIncrement(); + int missed = 1; + long e = emitted; + Subscriber<? super C> a = downstream; + SpscLinkedArrayQueue<C> q = queue; + + for (;;) { + long r = requested.get(); + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d && errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } + + C v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + e++; + } - p.subscribe(bcs); - } + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + if (errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } else if (q.isEmpty()) { + a.onComplete(); + return; + } + } + } - void openFinished(Disposable d) { - if (resources.remove(d)) { - if (windows.decrementAndGet() == 0) { - complete(); + emitted = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; } } } - void close(U b, Disposable d) { + static final class BufferOpenSubscriber<Open> + extends AtomicReference<Subscription> + implements FlowableSubscriber<Open>, Disposable { - boolean e; - synchronized (this) { - e = buffers.remove(b); + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundarySubscriber<?, ?, Open, ?> parent; + + BufferOpenSubscriber(BufferBoundarySubscriber<?, ?, Open, ?> parent) { + this.parent = parent; } - if (e) { - fastPathOrderedEmitMax(b, false, this); + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } - if (resources.remove(d)) { - if (windows.decrementAndGet() == 0) { - complete(); - } + @Override + public void onNext(Open t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + lazySet(SubscriptionHelper.CANCELLED); + parent.boundaryError(this, t); + } + + @Override + public void onComplete() { + lazySet(SubscriptionHelper.CANCELLED); + parent.openComplete(this); + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; } } } - static final class BufferOpenSubscriber<T, U extends Collection<? super T>, Open, Close> - extends DisposableSubscriber<Open> { - final BufferBoundarySubscriber<T, U, Open, Close> parent; + static final class BufferCloseSubscriber<T, C extends Collection<? super T>> + extends AtomicReference<Subscription> + implements FlowableSubscriber<Object>, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundarySubscriber<T, C, ?, ?> parent; - boolean done; + final long index; - BufferOpenSubscriber(BufferBoundarySubscriber<T, U, Open, Close> parent) { + BufferCloseSubscriber(BufferBoundarySubscriber<T, C, ?, ?> parent, long index) { this.parent = parent; + this.index = index; } + @Override - public void onNext(Open t) { - if (done) { - return; + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + parent.close(this, index); } - parent.open(t); } @Override public void onError(Throwable t) { - if (done) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.boundaryError(this, t); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - parent.onError(t); } @Override public void onComplete() { - if (done) { - return; + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.close(this, index); } - done = true; - parent.openFinished(this); - } - } - - static final class BufferCloseSubscriber<T, U extends Collection<? super T>, Open, Close> - extends DisposableSubscriber<Close> { - final BufferBoundarySubscriber<T, U, Open, Close> parent; - final U value; - boolean done; - BufferCloseSubscriber(U value, BufferBoundarySubscriber<T, U, Open, Close> parent) { - this.parent = parent; - this.value = value; } @Override - public void onNext(Close t) { - onComplete(); - } - - @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - parent.onError(t); + public void dispose() { + SubscriptionHelper.cancel(this); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - parent.close(value, this); + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java index af98d86cfe..8c544d958d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java @@ -53,7 +53,7 @@ static final class BufferBoundarySupplierSubscriber<T, U extends Collection<? su final Callable<U> bufferSupplier; final Callable<? extends Publisher<B>> boundarySupplier; - Subscription s; + Subscription upstream; final AtomicReference<Disposable> other = new AtomicReference<Disposable>(); @@ -68,12 +68,12 @@ static final class BufferBoundarySupplierSubscriber<T, U extends Collection<? su @Override public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { + if (!SubscriptionHelper.validate(this.upstream, s)) { return; } - this.s = s; + this.upstream = s; - Subscriber<? super U> actual = this.actual; + Subscriber<? super U> actual = this.downstream; U b; @@ -127,7 +127,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { cancel(); - actual.onError(t); + downstream.onError(t); } @Override @@ -143,7 +143,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, actual, false, this, this); + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); } } @@ -156,7 +156,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); disposeOther(); if (enter()) { @@ -178,7 +178,7 @@ void next() { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -189,36 +189,32 @@ void next() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancelled = true; - s.cancel(); - actual.onError(ex); + upstream.cancel(); + downstream.onError(ex); return; } BufferBoundarySubscriber<T, U, B> bs = new BufferBoundarySubscriber<T, U, B>(this); - Disposable o = other.get(); - - if (!other.compareAndSet(o, bs)) { - return; - } - - U b; - synchronized (this) { - b = buffer; - if (b == null) { - return; + if (DisposableHelper.replace(other, bs)) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; } - buffer = next; - } - boundary.subscribe(bs); + boundary.subscribe(bs); - fastPathEmitMax(b, false, this); + fastPathEmitMax(b, false, this); + } } @Override public void dispose() { - s.cancel(); + upstream.cancel(); disposeOther(); } @@ -229,7 +225,7 @@ public boolean isDisposed() { @Override public boolean accept(Subscriber<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); return true; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java index 5ed7f84b08..82215ae967 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java @@ -50,7 +50,7 @@ static final class BufferExactBoundarySubscriber<T, U extends Collection<? super final Callable<U> bufferSupplier; final Publisher<B> boundary; - Subscription s; + Subscription upstream; Disposable other; @@ -65,10 +65,10 @@ static final class BufferExactBoundarySubscriber<T, U extends Collection<? super @Override public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { + if (!SubscriptionHelper.validate(this.upstream, s)) { return; } - this.s = s; + this.upstream = s; U b; @@ -78,7 +78,7 @@ public void onSubscribe(Subscription s) { Exceptions.throwIfFatal(e); cancelled = true; s.cancel(); - EmptySubscription.error(e, actual); + EmptySubscription.error(e, downstream); return; } @@ -87,7 +87,7 @@ public void onSubscribe(Subscription s) { BufferBoundarySubscriber<T, U, B> bs = new BufferBoundarySubscriber<T, U, B>(this); other = bs; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (!cancelled) { s.request(Long.MAX_VALUE); @@ -110,7 +110,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { cancel(); - actual.onError(t); + downstream.onError(t); } @Override @@ -126,7 +126,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, actual, false, this, this); + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); } } @@ -140,7 +140,7 @@ public void cancel() { if (!cancelled) { cancelled = true; other.dispose(); - s.cancel(); + upstream.cancel(); if (enter()) { queue.clear(); @@ -157,7 +157,7 @@ void next() { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -185,7 +185,7 @@ public boolean isDisposed() { @Override public boolean accept(Subscriber<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); return true; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java index f6320ced12..06736603f1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java @@ -78,7 +78,6 @@ protected void subscribeActual(Subscriber<? super U> s) { bufferSupplier, timespan, timeskip, unit, w)); } - static final class BufferExactUnboundedSubscriber<T, U extends Collection<? super T>> extends QueueDrainSubscriber<T, U, U> implements Subscription, Runnable, Disposable { final Callable<U> bufferSupplier; @@ -86,7 +85,7 @@ static final class BufferExactUnboundedSubscriber<T, U extends Collection<? supe final TimeUnit unit; final Scheduler scheduler; - Subscription s; + Subscription upstream; U buffer; @@ -104,8 +103,8 @@ static final class BufferExactUnboundedSubscriber<T, U extends Collection<? supe @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; U b; @@ -114,13 +113,13 @@ public void onSubscribe(Subscription s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - EmptySubscription.error(e, actual); + EmptySubscription.error(e, downstream); return; } buffer = b; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (!cancelled) { s.request(Long.MAX_VALUE); @@ -149,7 +148,7 @@ public void onError(Throwable t) { synchronized (this) { buffer = null; } - actual.onError(t); + downstream.onError(t); } @Override @@ -166,7 +165,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, actual, false, this, this); + QueueDrainHelper.drainMaxLoop(queue, downstream, false, null, this); } } @@ -177,8 +176,8 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); - + cancelled = true; + upstream.cancel(); DisposableHelper.dispose(timer); } @@ -191,7 +190,7 @@ public void run() { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -199,14 +198,10 @@ public void run() { synchronized (this) { current = buffer; - if (current != null) { - buffer = next; + if (current == null) { + return; } - } - - if (current == null) { - DisposableHelper.dispose(timer); - return; + buffer = next; } fastPathEmitMax(current, false, this); @@ -214,7 +209,7 @@ public void run() { @Override public boolean accept(Subscriber<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); return true; } @@ -238,8 +233,7 @@ static final class BufferSkipBoundedSubscriber<T, U extends Collection<? super T final Worker w; final List<U> buffers; - Subscription s; - + Subscription upstream; BufferSkipBoundedSubscriber(Subscriber<? super U> actual, Callable<U> bufferSupplier, long timespan, @@ -255,10 +249,10 @@ static final class BufferSkipBoundedSubscriber<T, U extends Collection<? super T @Override public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { + if (!SubscriptionHelper.validate(this.upstream, s)) { return; } - this.s = s; + this.upstream = s; final U b; // NOPMD @@ -268,13 +262,13 @@ public void onSubscribe(Subscription s) { Exceptions.throwIfFatal(e); w.dispose(); s.cancel(); - EmptySubscription.error(e, actual); + EmptySubscription.error(e, downstream); return; } buffers.add(b); - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); @@ -297,7 +291,7 @@ public void onError(Throwable t) { done = true; w.dispose(); clear(); - actual.onError(t); + downstream.onError(t); } @Override @@ -313,7 +307,7 @@ public void onComplete() { } done = true; if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, actual, false, w, this); + QueueDrainHelper.drainMaxLoop(queue, downstream, false, w, this); } } @@ -324,9 +318,10 @@ public void request(long n) { @Override public void cancel() { - clear(); - s.cancel(); + cancelled = true; + upstream.cancel(); w.dispose(); + clear(); } void clear() { @@ -347,7 +342,7 @@ public void run() { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -398,7 +393,7 @@ static final class BufferExactBoundedSubscriber<T, U extends Collection<? super Disposable timer; - Subscription s; + Subscription upstream; long producerIndex; @@ -420,10 +415,10 @@ static final class BufferExactBoundedSubscriber<T, U extends Collection<? super @Override public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { + if (!SubscriptionHelper.validate(this.upstream, s)) { return; } - this.s = s; + this.upstream = s; U b; @@ -433,13 +428,13 @@ public void onSubscribe(Subscription s) { Exceptions.throwIfFatal(e); w.dispose(); s.cancel(); - EmptySubscription.error(e, actual); + EmptySubscription.error(e, downstream); return; } buffer = b; - actual.onSubscribe(this); + downstream.onSubscribe(this); timer = w.schedulePeriodically(this, timespan, timespan, unit); @@ -476,7 +471,7 @@ public void onNext(T t) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -494,7 +489,7 @@ public void onError(Throwable t) { synchronized (this) { buffer = null; } - actual.onError(t); + downstream.onError(t); w.dispose(); } @@ -506,13 +501,14 @@ public void onComplete() { buffer = null; } - queue.offer(b); - done = true; - if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, actual, false, this, this); + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + } + w.dispose(); } - - w.dispose(); } @Override @@ -521,7 +517,6 @@ public boolean accept(Subscriber<? super U> a, U v) { return true; } - @Override public void request(long n) { requested(n); @@ -540,7 +535,7 @@ public void dispose() { synchronized (this) { buffer = null; } - s.cancel(); + upstream.cancel(); w.dispose(); } @@ -558,7 +553,7 @@ public void run() { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java index a4294474be..830b0984ac 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java @@ -19,7 +19,7 @@ import io.reactivex.*; import io.reactivex.internal.subscriptions.SubscriptionHelper; -import io.reactivex.internal.util.*; +import io.reactivex.internal.util.BackpressureHelper; import io.reactivex.plugins.RxJavaPlugins; /** @@ -28,38 +28,94 @@ * * @param <T> the source element type */ -public final class FlowableCache<T> extends AbstractFlowableWithUpstream<T, T> { - /** The cache and replay state. */ - final CacheState<T> state; +public final class FlowableCache<T> extends AbstractFlowableWithUpstream<T, T> +implements FlowableSubscriber<T> { + /** + * The subscription to the source should happen at most once. + */ final AtomicBoolean once; /** - * Private constructor because state needs to be shared between the Observable body and - * the onSubscribe function. - * @param source the upstream source whose signals to cache - * @param capacityHint the capacity hint + * The number of items per cached nodes. + */ + final int capacityHint; + + /** + * The current known array of subscriber state to notify. + */ + final AtomicReference<CacheSubscription<T>[]> subscribers; + + /** + * A shared instance of an empty array of subscribers to avoid creating + * a new empty array when all subscribers cancel. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] EMPTY = new CacheSubscription[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember subscribers anymore. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] TERMINATED = new CacheSubscription[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node<T> head; + + /** + * The current tail of the linked structure holding the items. + */ + Node<T> tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #subscribers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming subscriber + * @param capacityHint the number of items expected (reduce allocation frequency) */ + @SuppressWarnings("unchecked") public FlowableCache(Flowable<T> source, int capacityHint) { super(source); - this.state = new CacheState<T>(source, capacityHint); + this.capacityHint = capacityHint; this.once = new AtomicBoolean(); + Node<T> n = new Node<T>(capacityHint); + this.head = n; + this.tail = n; + this.subscribers = new AtomicReference<CacheSubscription<T>[]>(EMPTY); } @Override protected void subscribeActual(Subscriber<? super T> t) { - // we can connect first because we replay everything anyway - ReplaySubscription<T> rp = new ReplaySubscription<T>(t, state); - state.addChild(rp); - - t.onSubscribe(rp); + CacheSubscription<T> consumer = new CacheSubscription<T>(t, this); + t.onSubscribe(consumer); + add(consumer); - // we ensure a single connection here to save an instance field of AtomicBoolean in state. if (!once.get() && once.compareAndSet(false, true)) { - state.connect(); + source.subscribe(this); + } else { + replay(consumer); } - - // no need to call rp.replay() here because the very first request will trigger it anyway } /** @@ -67,7 +123,7 @@ protected void subscribeActual(Subscriber<? super T> t) { * @return true if already connected */ /* public */boolean isConnected() { - return state.isConnected; + return once.get(); } /** @@ -75,311 +131,287 @@ protected void subscribeActual(Subscriber<? super T> t) { * @return true if the cache has Subscribers */ /* public */ boolean hasSubscribers() { - return state.subscribers.get().length != 0; + return subscribers.get().length != 0; } /** * Returns the number of events currently cached. * @return the number of currently cached event count */ - /* public */ int cachedEventCount() { - return state.size(); + /* public */ long cachedEventCount() { + return size; } /** - * Contains the active child subscribers and the values to replay. - * - * @param <T> the value type of the cached items + * Atomically adds the consumer to the {@link #subscribers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add */ - static final class CacheState<T> extends LinkedArrayList implements FlowableSubscriber<T> { - /** The source observable to connect to. */ - final Flowable<T> source; - /** Holds onto the subscriber connected to source. */ - final AtomicReference<Subscription> connection = new AtomicReference<Subscription>(); - /** Guarded by connection (not this). */ - final AtomicReference<ReplaySubscription<T>[]> subscribers; - /** The default empty array of subscribers. */ - @SuppressWarnings("rawtypes") - static final ReplaySubscription[] EMPTY = new ReplaySubscription[0]; - /** The default empty array of subscribers. */ - @SuppressWarnings("rawtypes") - static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0]; - - /** Set to true after connection. */ - volatile boolean isConnected; - /** - * Indicates that the source has completed emitting values or the - * Observable was forcefully terminated. - */ - boolean sourceDone; + void add(CacheSubscription<T> consumer) { + for (;;) { + CacheSubscription<T>[] current = subscribers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; - @SuppressWarnings("unchecked") - CacheState(Flowable<T> source, int capacityHint) { - super(capacityHint); - this.source = source; - this.subscribers = new AtomicReference<ReplaySubscription<T>[]>(EMPTY); + @SuppressWarnings("unchecked") + CacheSubscription<T>[] next = new CacheSubscription[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (subscribers.compareAndSet(current, next)) { + return; + } } - /** - * Adds a ReplaySubscription to the subscribers array atomically. - * @param p the target ReplaySubscription wrapping a downstream Subscriber with state - */ - public void addChild(ReplaySubscription<T> p) { - // guarding by connection to save on allocating another object - // thus there are two distinct locks guarding the value-addition and child come-and-go - for (;;) { - ReplaySubscription<T>[] a = subscribers.get(); - if (a == TERMINATED) { - return; - } - int n = a.length; - @SuppressWarnings("unchecked") - ReplaySubscription<T>[] b = new ReplaySubscription[n + 1]; - System.arraycopy(a, 0, b, 0, n); - b[n] = p; - if (subscribers.compareAndSet(a, b)) { - return; + } + + /** + * Atomically removes the consumer from the {@link #subscribers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheSubscription<T> consumer) { + for (;;) { + CacheSubscription<T>[] current = subscribers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; } } + + if (j < 0) { + return; + } + CacheSubscription<T>[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheSubscription[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (subscribers.compareAndSet(current, next)) { + return; + } } - /** - * Removes the ReplaySubscription (if present) from the subscribers array atomically. - * @param p the target ReplaySubscription wrapping a downstream Subscriber with state - */ - @SuppressWarnings("unchecked") - public void removeChild(ReplaySubscription<T> p) { - for (;;) { - ReplaySubscription<T>[] a = subscribers.get(); - int n = a.length; - if (n == 0) { - return; - } - int j = -1; - for (int i = 0; i < n; i++) { - if (a[i].equals(p)) { - j = i; - break; - } - } - if (j < 0) { - return; - } + } - ReplaySubscription<T>[] b; - if (n == 1) { - b = EMPTY; + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheSubscription<T> consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node<T> node = consumer.node; + AtomicLong requested = consumer.requested; + Subscriber<? super T> downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); } else { - b = new ReplaySubscription[n - 1]; - System.arraycopy(a, 0, b, 0, j); - System.arraycopy(a, j + 1, b, j, n - j - 1); + downstream.onComplete(); } - if (subscribers.compareAndSet(a, b)) { + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // see how many items the consumer has requested in total so far + long consumerRequested = requested.get(); + // MIN_VALUE indicates a cancelled consumer, we stop replaying + if (consumerRequested == Long.MIN_VALUE) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; return; } + // if the consumer has requested more and there is more, we will emit an item + if (consumerRequested != index) { + + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; + } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; + } } - } - @Override - public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(connection, s)) { - s.request(Long.MAX_VALUE); + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; } } + } - /** - * Connects the cache to the source. - * Make sure this is called only once. - */ - public void connect() { - source.subscribe(this); - isConnected = true; + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node<T> n = new Node<T>(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; } - @Override - public void onNext(T t) { - if (!sourceDone) { - Object o = NotificationLite.next(t); - add(o); - for (ReplaySubscription<?> rp : subscribers.get()) { - rp.replay(); - } - } + size++; + for (CacheSubscription<T> consumer : subscribers.get()) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onError(Throwable e) { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.error(e); - add(o); - SubscriptionHelper.cancel(connection); - for (ReplaySubscription<?> rp : subscribers.getAndSet(TERMINATED)) { - rp.replay(); - } - } else { - RxJavaPlugins.onError(e); - } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; } - @SuppressWarnings("unchecked") - @Override - public void onComplete() { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.complete(); - add(o); - SubscriptionHelper.cancel(connection); - for (ReplaySubscription<?> rp : subscribers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + error = t; + done = true; + for (CacheSubscription<T> consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheSubscription<T> consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); } } /** - * Keeps track of the current request amount and the replay position for a child Subscriber. - * - * @param <T> + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param <T> the value type */ - static final class ReplaySubscription<T> - extends AtomicInteger implements Subscription { + static final class CacheSubscription<T> extends AtomicInteger + implements Subscription { + + private static final long serialVersionUID = 6770240836423125754L; + + final Subscriber<? super T> downstream; - private static final long serialVersionUID = -2557562030197141021L; - private static final long CANCELLED = -1; - /** The actual child subscriber. */ - final Subscriber<? super T> child; - /** The cache state object. */ - final CacheState<T> state; + final FlowableCache<T> parent; final AtomicLong requested; + Node<T> node; + + int offset; + + long index; + /** - * Contains the reference to the buffer segment in replay. - * Accessed after reading state.size() and when emitting == true. - */ - Object[] currentBuffer; - /** - * Contains the index into the currentBuffer where the next value is expected. - * Accessed after reading state.size() and when emitting == true. - */ - int currentIndexInBuffer; - /** - * Contains the absolute index up until the values have been replayed so far. + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items */ - int index; - - ReplaySubscription(Subscriber<? super T> child, CacheState<T> state) { - this.child = child; - this.state = state; + CacheSubscription(Subscriber<? super T> downstream, FlowableCache<T> parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; this.requested = new AtomicLong(); } + @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { - for (;;) { - long r = requested.get(); - if (r == CANCELLED) { - return; - } - long u = BackpressureHelper.addCap(r, n); - if (requested.compareAndSet(r, u)) { - replay(); - return; - } - } + BackpressureHelper.addCancel(requested, n); + parent.replay(this); } } @Override public void cancel() { - if (requested.getAndSet(CANCELLED) != CANCELLED) { - state.removeChild(this); + if (requested.getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); } } + } + + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param <T> the element type + */ + static final class Node<T> { /** - * Continue replaying available values if there are requests for them. + * The array of values held by this node. */ - public void replay() { - if (getAndIncrement() != 0) { - return; - } - - int missed = 1; - final Subscriber<? super T> child = this.child; - AtomicLong rq = requested; - - for (;;) { + final T[] values; - long r = rq.get(); - - if (r < 0L) { - return; - } - - // read the size, if it is non-zero, we can safely read the head and - // read values up to the given absolute index - int s = state.size(); - if (s != 0) { - Object[] b = currentBuffer; - - // latch onto the very first buffer now that it is available. - if (b == null) { - b = state.head(); - currentBuffer = b; - } - final int n = b.length - 1; - int j = index; - int k = currentIndexInBuffer; - int valuesProduced = 0; - - while (j < s && r > 0) { - if (rq.get() == CANCELLED) { - return; - } - if (k == n) { - b = (Object[])b[n]; - k = 0; - } - Object o = b[k]; - - if (NotificationLite.accept(o, child)) { - return; - } - - k++; - j++; - r--; - valuesProduced++; - } - - if (rq.get() == CANCELLED) { - return; - } - - if (r == 0) { - Object o = b[k]; - if (NotificationLite.isComplete(o)) { - child.onComplete(); - return; - } else - if (NotificationLite.isError(o)) { - child.onError(NotificationLite.getError(o)); - return; - } - } - - if (valuesProduced != 0) { - BackpressureHelper.producedCancel(rq, valuesProduced); - } - - index = j; - currentIndexInBuffer = k; - currentBuffer = b; - } + /** + * The next node if not null. + */ + volatile Node<T> next; - missed = addAndGet(-missed); - if (missed == 0) { - break; - } - } + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java index 750a3a5d30..ff858505a0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java @@ -55,7 +55,7 @@ static final class CollectSubscriber<T, U> extends DeferredScalarSubscription<U> final U u; - Subscription s; + Subscription upstream; boolean done; @@ -67,9 +67,9 @@ static final class CollectSubscriber<T, U> extends DeferredScalarSubscription<U> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -83,7 +83,7 @@ public void onNext(T t) { collector.accept(u, t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); } } @@ -95,7 +95,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -110,7 +110,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java index aaab14bd23..c5d3dc188d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java @@ -40,16 +40,16 @@ public FlowableCollectSingle(Flowable<T> source, Callable<? extends U> initialSu } @Override - protected void subscribeActual(SingleObserver<? super U> s) { + protected void subscribeActual(SingleObserver<? super U> observer) { U u; try { u = ObjectHelper.requireNonNull(initialSupplier.call(), "The initialSupplier returned a null value"); } catch (Throwable e) { - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - source.subscribe(new CollectSubscriber<T, U>(s, u, collector)); + source.subscribe(new CollectSubscriber<T, U>(observer, u, collector)); } @Override @@ -59,27 +59,27 @@ public Flowable<U> fuseToFlowable() { static final class CollectSubscriber<T, U> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super U> actual; + final SingleObserver<? super U> downstream; final BiConsumer<? super U, ? super T> collector; final U u; - Subscription s; + Subscription upstream; boolean done; CollectSubscriber(SingleObserver<? super U> actual, U u, BiConsumer<? super U, ? super T> collector) { - this.actual = actual; + this.downstream = actual; this.collector = collector; this.u = u; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -93,7 +93,7 @@ public void onNext(T t) { collector.accept(u, t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); } } @@ -105,8 +105,8 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override @@ -115,19 +115,19 @@ public void onComplete() { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(u); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(u); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java index cbdda4a4bc..c690701fcf 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java @@ -136,7 +136,6 @@ public void subscribeActual(Subscriber<? super R> s) { return; } - CombineLatestCoordinator<T, R> coordinator = new CombineLatestCoordinator<T, R>(s, combiner, n, bufferSize, delayErrors); @@ -148,10 +147,9 @@ public void subscribeActual(Subscriber<? super R> s) { static final class CombineLatestCoordinator<T, R> extends BasicIntQueueSubscription<R> { - private static final long serialVersionUID = -5082275438355852221L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super Object[], ? extends R> combiner; @@ -180,7 +178,7 @@ static final class CombineLatestCoordinator<T, R> CombineLatestCoordinator(Subscriber<? super R> actual, Function<? super Object[], ? extends R> combiner, int n, int bufferSize, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; @SuppressWarnings("unchecked") CombineLatestInnerSubscriber<T>[] a = new CombineLatestInnerSubscriber[n]; @@ -289,7 +287,7 @@ void innerError(int index, Throwable e) { } void drainOutput() { - final Subscriber<? super R> a = actual; + final Subscriber<? super R> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; int missed = 1; @@ -331,7 +329,7 @@ void drainOutput() { @SuppressWarnings("unchecked") void drainAsync() { - final Subscriber<? super R> a = actual; + final Subscriber<? super R> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; int missed = 1; @@ -494,7 +492,6 @@ static final class CombineLatestInnerSubscriber<T> extends AtomicReference<Subscription> implements FlowableSubscriber<T> { - private static final long serialVersionUID = -8730235182291002949L; final CombineLatestCoordinator<T, ?> parent; @@ -516,9 +513,7 @@ static final class CombineLatestInnerSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(prefetch); - } + SubscriptionHelper.setOnce(this, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java index c2ca5de3b3..4d7cd06c5b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java @@ -44,7 +44,7 @@ static final class ConcatArraySubscriber<T> extends SubscriptionArbiter implemen private static final long serialVersionUID = -8158322871608889516L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Publisher<? extends T>[] sources; @@ -58,8 +58,9 @@ static final class ConcatArraySubscriber<T> extends SubscriptionArbiter implemen long produced; - ConcatArraySubscriber(Publisher<? extends T>[] sources, boolean delayError, Subscriber<? super T> actual) { - this.actual = actual; + ConcatArraySubscriber(Publisher<? extends T>[] sources, boolean delayError, Subscriber<? super T> downstream) { + super(false); + this.downstream = downstream; this.sources = sources; this.delayError = delayError; this.wip = new AtomicInteger(); @@ -73,7 +74,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { produced++; - actual.onNext(t); + downstream.onNext(t); } @Override @@ -87,7 +88,7 @@ public void onError(Throwable t) { list.add(t); onComplete(); } else { - actual.onError(t); + downstream.onError(t); } } @@ -103,12 +104,12 @@ public void onComplete() { List<Throwable> list = errors; if (list != null) { if (list.size() == 1) { - actual.onError(list.get(0)); + downstream.onError(list.get(0)); } else { - actual.onError(new CompositeException(list)); + downstream.onError(new CompositeException(list)); } } else { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -127,7 +128,7 @@ public void onComplete() { i++; continue; } else { - actual.onError(ex); + downstream.onError(ex); return; } } else { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java index 6aee2f7c80..64df7cc122 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.flowable; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.reactivestreams.*; @@ -80,7 +80,7 @@ abstract static class BaseConcatMapSubscriber<T, R> final int limit; - Subscription s; + Subscription upstream; int consumed; @@ -108,12 +108,12 @@ abstract static class BaseConcatMapSubscriber<T, R> @Override public final void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription<T> f = (QueueSubscription<T>)s; - int m = f.requestFusion(QueueSubscription.ANY); + int m = f.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { sourceMode = m; queue = f; @@ -151,7 +151,7 @@ public final void onSubscribe(Subscription s) { public final void onNext(T t) { if (sourceMode != QueueSubscription.ASYNC) { if (!queue.offer(t)) { - s.cancel(); + upstream.cancel(); onError(new IllegalStateException("Queue full?!")); return; } @@ -173,14 +173,12 @@ public final void innerComplete() { } - static final class ConcatMapImmediate<T, R> extends BaseConcatMapSubscriber<T, R> { - private static final long serialVersionUID = 7898995095634264146L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final AtomicInteger wip; @@ -188,13 +186,13 @@ static final class ConcatMapImmediate<T, R> Function<? super T, ? extends Publisher<? extends R>> mapper, int prefetch) { super(mapper, prefetch); - this.actual = actual; + this.downstream = actual; this.wip = new AtomicInteger(); } @Override void subscribeActual() { - actual.onSubscribe(this); + downstream.onSubscribe(this); } @Override @@ -203,7 +201,7 @@ public void onError(Throwable t) { inner.cancel(); if (getAndIncrement() == 0) { - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); } } else { RxJavaPlugins.onError(t); @@ -213,21 +211,21 @@ public void onError(Throwable t) { @Override public void innerNext(R value) { if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(value); + downstream.onNext(value); if (compareAndSet(1, 0)) { return; } - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); } } @Override public void innerError(Throwable e) { if (errors.addThrowable(e)) { - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); } } else { RxJavaPlugins.onError(e); @@ -245,7 +243,7 @@ public void cancel() { cancelled = true; inner.cancel(); - s.cancel(); + upstream.cancel(); } } @@ -266,16 +264,16 @@ void drain() { v = queue.poll(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } boolean empty = v == null; if (d && empty) { - actual.onComplete(); + downstream.onComplete(); return; } @@ -287,9 +285,9 @@ void drain() { } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } @@ -297,13 +295,12 @@ void drain() { int c = consumed + 1; if (c == limit) { consumed = 0; - s.request(c); + upstream.request(c); } else { consumed = c; } } - if (p instanceof Callable) { @SuppressWarnings("unchecked") Callable<R> callable = (Callable<R>) p; @@ -314,29 +311,28 @@ void drain() { vr = callable.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } - if (vr == null) { continue; } if (inner.isUnbounded()) { if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(vr); + downstream.onNext(vr); if (!compareAndSet(1, 0)) { - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } } continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription<R>(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription<R>(vr, inner)); } } else { @@ -353,21 +349,21 @@ void drain() { } } - static final class WeakScalarSubscription<T> implements Subscription { - final Subscriber<? super T> actual; + static final class SimpleScalarSubscription<T> + extends AtomicBoolean + implements Subscription { + final Subscriber<? super T> downstream; final T value; - boolean once; - WeakScalarSubscription(T value, Subscriber<? super T> actual) { + SimpleScalarSubscription(T value, Subscriber<? super T> downstream) { this.value = value; - this.actual = actual; + this.downstream = downstream; } @Override public void request(long n) { - if (n > 0 && !once) { - once = true; - Subscriber<? super T> a = actual; + if (n > 0 && compareAndSet(false, true)) { + Subscriber<? super T> a = downstream; a.onNext(value); a.onComplete(); } @@ -382,10 +378,9 @@ public void cancel() { static final class ConcatMapDelayed<T, R> extends BaseConcatMapSubscriber<T, R> { - private static final long serialVersionUID = -2945777694260521066L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final boolean veryEnd; @@ -393,13 +388,13 @@ static final class ConcatMapDelayed<T, R> Function<? super T, ? extends Publisher<? extends R>> mapper, int prefetch, boolean veryEnd) { super(mapper, prefetch); - this.actual = actual; + this.downstream = actual; this.veryEnd = veryEnd; } @Override void subscribeActual() { - actual.onSubscribe(this); + downstream.onSubscribe(this); } @Override @@ -414,15 +409,14 @@ public void onError(Throwable t) { @Override public void innerNext(R value) { - actual.onNext(value); + downstream.onNext(value); } - @Override public void innerError(Throwable e) { if (errors.addThrowable(e)) { if (!veryEnd) { - s.cancel(); + upstream.cancel(); done = true; } active = false; @@ -443,7 +437,7 @@ public void cancel() { cancelled = true; inner.cancel(); - s.cancel(); + upstream.cancel(); } } @@ -463,7 +457,7 @@ void drain() { if (d && !veryEnd) { Throwable ex = errors.get(); if (ex != null) { - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } } @@ -474,9 +468,9 @@ void drain() { v = queue.poll(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } @@ -485,9 +479,9 @@ void drain() { if (d && empty) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -500,9 +494,9 @@ void drain() { } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); + downstream.onError(errors.terminate()); return; } @@ -510,7 +504,7 @@ void drain() { int c = consumed + 1; if (c == limit) { consumed = 0; - s.request(c); + upstream.request(c); } else { consumed = c; } @@ -526,10 +520,13 @@ void drain() { vr = supplier.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); errors.addThrowable(e); - actual.onError(errors.terminate()); - return; + if (!veryEnd) { + upstream.cancel(); + downstream.onError(errors.terminate()); + return; + } + vr = null; } if (vr == null) { @@ -537,11 +534,11 @@ void drain() { } if (inner.isUnbounded()) { - actual.onNext(vr); + downstream.onNext(vr); continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription<R>(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription<R>(vr, inner)); } } else { active = true; @@ -570,7 +567,6 @@ static final class ConcatMapInner<R> extends SubscriptionArbiter implements FlowableSubscriber<R> { - private static final long serialVersionUID = 897683679971470653L; final ConcatMapSupport<R> parent; @@ -578,6 +574,7 @@ static final class ConcatMapInner<R> long produced; ConcatMapInner(ConcatMapSupport<R> parent) { + super(false); this.parent = parent; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java index 6d1af9101a..8acad8ac69 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java @@ -62,7 +62,7 @@ static final class ConcatMapEagerDelayErrorSubscriber<T, R> private static final long serialVersionUID = -4255299542215038287L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends Publisher<? extends R>> mapper; @@ -78,7 +78,7 @@ static final class ConcatMapEagerDelayErrorSubscriber<T, R> final SpscLinkedArrayQueue<InnerQueuedSubscriber<R>> subscribers; - Subscription s; + Subscription upstream; volatile boolean cancelled; @@ -89,7 +89,7 @@ static final class ConcatMapEagerDelayErrorSubscriber<T, R> ConcatMapEagerDelayErrorSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends Publisher<? extends R>> mapper, int maxConcurrency, int prefetch, ErrorMode errorMode) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.maxConcurrency = maxConcurrency; this.prefetch = prefetch; @@ -101,10 +101,10 @@ static final class ConcatMapEagerDelayErrorSubscriber<T, R> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(maxConcurrency == Integer.MAX_VALUE ? Long.MAX_VALUE : maxConcurrency); } @@ -119,7 +119,7 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -132,10 +132,6 @@ public void onNext(T t) { subscribers.offer(inner); - if (cancelled) { - return; - } - p.subscribe(inner); if (cancelled) { @@ -166,7 +162,7 @@ public void cancel() { return; } cancelled = true; - s.cancel(); + upstream.cancel(); drainAndCancel(); } @@ -180,7 +176,12 @@ void drainAndCancel() { } void cancelAll() { - InnerQueuedSubscriber<R> inner; + InnerQueuedSubscriber<R> inner = current; + current = null; + + if (inner != null) { + inner.cancel(); + } while ((inner = subscribers.poll()) != null) { inner.cancel(); @@ -210,7 +211,7 @@ public void innerError(InnerQueuedSubscriber<R> inner, Throwable e) { if (errors.addThrowable(e)) { inner.setDone(); if (errorMode != ErrorMode.END) { - s.cancel(); + upstream.cancel(); } drain(); } else { @@ -232,7 +233,7 @@ public void drain() { int missed = 1; InnerQueuedSubscriber<R> inner = current; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; ErrorMode em = errorMode; for (;;) { @@ -313,7 +314,7 @@ public void drain() { if (d && empty) { inner = null; current = null; - s.request(1); + upstream.request(1); continueNextSource = true; break; } @@ -354,7 +355,7 @@ public void drain() { if (d && empty) { inner = null; current = null; - s.request(1); + upstream.request(1); continueNextSource = true; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java index a88e05953d..43785a6fc5 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java @@ -22,9 +22,10 @@ /** * ConcatMapEager which works with an arbitrary Publisher source. + * <p>History: 2.0.7 - experimental * @param <T> the input value type * @param <R> the output type - * @since 2.0.7 - experimental + * @since 2.1 */ public final class FlowableConcatMapEagerPublisher<T, R> extends Flowable<R> { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java new file mode 100644 index 0000000000..8928d20139 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Completable + * and terminate when it terminates. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithCompletable<T> extends AbstractFlowableWithUpstream<T, T> { + + final CompletableSource other; + + public FlowableConcatWithCompletable(Flowable<T> source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new ConcatWithSubscriber<T>(s, other)); + } + + static final class ConcatWithSubscriber<T> + extends AtomicReference<Disposable> + implements FlowableSubscriber<T>, CompletableObserver, Subscription { + + private static final long serialVersionUID = -7346385463600070225L; + + final Subscriber<? super T> downstream; + + Subscription upstream; + + CompletableSource other; + + boolean inCompletable; + + ConcatWithSubscriber(Subscriber<? super T> actual, CompletableSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + if (inCompletable) { + downstream.onComplete(); + } else { + inCompletable = true; + upstream = SubscriptionHelper.CANCELLED; + CompletableSource cs = other; + other = null; + cs.subscribe(this); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java new file mode 100644 index 0000000000..0081d91feb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Maybe, + * signal its success value followed by a completion or signal its error or completion signal as is. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithMaybe<T> extends AbstractFlowableWithUpstream<T, T> { + + final MaybeSource<? extends T> other; + + public FlowableConcatWithMaybe(Flowable<T> source, MaybeSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new ConcatWithSubscriber<T>(s, other)); + } + + static final class ConcatWithSubscriber<T> + extends SinglePostCompleteSubscriber<T, T> + implements MaybeObserver<T> { + + private static final long serialVersionUID = -7346385463600070225L; + + final AtomicReference<Disposable> otherDisposable; + + MaybeSource<? extends T> other; + + boolean inMaybe; + + ConcatWithSubscriber(Subscriber<? super T> actual, MaybeSource<? extends T> other) { + super(actual); + this.other = other; + this.otherDisposable = new AtomicReference<Disposable>(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(otherDisposable, d); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onSuccess(T t) { + complete(t); + } + + @Override + public void onComplete() { + if (inMaybe) { + downstream.onComplete(); + } else { + inMaybe = true; + upstream = SubscriptionHelper.CANCELLED; + MaybeSource<? extends T> ms = other; + other = null; + ms.subscribe(this); + } + } + + @Override + public void cancel() { + super.cancel(); + DisposableHelper.dispose(otherDisposable); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java new file mode 100644 index 0000000000..23c60ea4fc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithSingle<T> extends AbstractFlowableWithUpstream<T, T> { + + final SingleSource<? extends T> other; + + public FlowableConcatWithSingle(Flowable<T> source, SingleSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new ConcatWithSubscriber<T>(s, other)); + } + + static final class ConcatWithSubscriber<T> + extends SinglePostCompleteSubscriber<T, T> + implements SingleObserver<T> { + + private static final long serialVersionUID = -7346385463600070225L; + + final AtomicReference<Disposable> otherDisposable; + + SingleSource<? extends T> other; + + ConcatWithSubscriber(Subscriber<? super T> actual, SingleSource<? extends T> other) { + super(actual); + this.other = other; + this.otherDisposable = new AtomicReference<Disposable>(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(otherDisposable, d); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onSuccess(T t) { + complete(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + SingleSource<? extends T> ss = other; + other = null; + ss.subscribe(this); + } + + @Override + public void cancel() { + super.cancel(); + DisposableHelper.dispose(otherDisposable); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java index 95bbfb0d2e..b29e2690a1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java @@ -32,22 +32,21 @@ protected void subscribeActual(Subscriber<? super Long> s) { static final class CountSubscriber extends DeferredScalarSubscription<Long> implements FlowableSubscriber<Object> { - private static final long serialVersionUID = 4973004223787171406L; - Subscription s; + Subscription upstream; long count; - CountSubscriber(Subscriber<? super Long> actual) { - super(actual); + CountSubscriber(Subscriber<? super Long> downstream) { + super(downstream); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -59,7 +58,7 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -70,7 +69,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java index 97c4c08c49..c43f0314f0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java @@ -30,8 +30,8 @@ public FlowableCountSingle(Flowable<T> source) { } @Override - protected void subscribeActual(SingleObserver<? super Long> s) { - source.subscribe(new CountSubscriber(s)); + protected void subscribeActual(SingleObserver<? super Long> observer) { + source.subscribe(new CountSubscriber(observer)); } @Override @@ -41,21 +41,21 @@ public Flowable<Long> fuseToFlowable() { static final class CountSubscriber implements FlowableSubscriber<Object>, Disposable { - final SingleObserver<? super Long> actual; + final SingleObserver<? super Long> downstream; - Subscription s; + Subscription upstream; long count; - CountSubscriber(SingleObserver<? super Long> actual) { - this.actual = actual; + CountSubscriber(SingleObserver<? super Long> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -67,25 +67,25 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(count); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(count); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java index a729b8dc0f..caf6d31101 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java @@ -28,7 +28,6 @@ import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; - public final class FlowableCreate<T> extends Flowable<T> { final FlowableOnSubscribe<T> source; @@ -210,8 +209,8 @@ void drainLoop() { } @Override - public void setDisposable(Disposable s) { - emitter.setDisposable(s); + public void setDisposable(Disposable d) { + emitter.setDisposable(d); } @Override @@ -233,6 +232,11 @@ public boolean isCancelled() { public FlowableEmitter<T> serialize() { return this; } + + @Override + public String toString() { + return emitter.toString(); + } } abstract static class BaseEmitter<T> @@ -240,12 +244,12 @@ abstract static class BaseEmitter<T> implements FlowableEmitter<T>, Subscription { private static final long serialVersionUID = 7326289992464377023L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SequentialDisposable serial; - BaseEmitter(Subscriber<? super T> actual) { - this.actual = actual; + BaseEmitter(Subscriber<? super T> downstream) { + this.downstream = downstream; this.serial = new SequentialDisposable(); } @@ -259,7 +263,7 @@ protected void complete() { return; } try { - actual.onComplete(); + downstream.onComplete(); } finally { serial.dispose(); } @@ -285,7 +289,7 @@ protected boolean error(Throwable e) { return false; } try { - actual.onError(e); + downstream.onError(e); } finally { serial.dispose(); } @@ -320,8 +324,8 @@ void onRequested() { } @Override - public final void setDisposable(Disposable s) { - serial.update(s); + public final void setDisposable(Disposable d) { + serial.update(d); } @Override @@ -338,15 +342,19 @@ public final long requested() { public final FlowableEmitter<T> serialize() { return new SerializedEmitter<T>(this); } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } } static final class MissingEmitter<T> extends BaseEmitter<T> { - private static final long serialVersionUID = 3776720187248809713L; - MissingEmitter(Subscriber<? super T> actual) { - super(actual); + MissingEmitter(Subscriber<? super T> downstream) { + super(downstream); } @Override @@ -356,7 +364,7 @@ public void onNext(T t) { } if (t != null) { - actual.onNext(t); + downstream.onNext(t); } else { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); return; @@ -376,8 +384,8 @@ abstract static class NoOverflowBaseAsyncEmitter<T> extends BaseEmitter<T> { private static final long serialVersionUID = 4127754106204442833L; - NoOverflowBaseAsyncEmitter(Subscriber<? super T> actual) { - super(actual); + NoOverflowBaseAsyncEmitter(Subscriber<? super T> downstream) { + super(downstream); } @Override @@ -392,7 +400,7 @@ public final void onNext(T t) { } if (get() != 0) { - actual.onNext(t); + downstream.onNext(t); BackpressureHelper.produced(this, 1); } else { onOverflow(); @@ -404,11 +412,10 @@ public final void onNext(T t) { static final class DropAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> { - private static final long serialVersionUID = 8360058422307496563L; - DropAsyncEmitter(Subscriber<? super T> actual) { - super(actual); + DropAsyncEmitter(Subscriber<? super T> downstream) { + super(downstream); } @Override @@ -420,11 +427,10 @@ void onOverflow() { static final class ErrorAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> { - private static final long serialVersionUID = 338953216916120960L; - ErrorAsyncEmitter(Subscriber<? super T> actual) { - super(actual); + ErrorAsyncEmitter(Subscriber<? super T> downstream) { + super(downstream); } @Override @@ -436,7 +442,6 @@ void onOverflow() { static final class BufferAsyncEmitter<T> extends BaseEmitter<T> { - private static final long serialVersionUID = 2427151001689639875L; final SpscLinkedArrayQueue<T> queue; @@ -506,7 +511,7 @@ void drain() { } int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final SpscLinkedArrayQueue<T> q = queue; for (;;) { @@ -579,7 +584,6 @@ void drain() { static final class LatestAsyncEmitter<T> extends BaseEmitter<T> { - private static final long serialVersionUID = 4023437720691792495L; final AtomicReference<T> queue; @@ -589,8 +593,8 @@ static final class LatestAsyncEmitter<T> extends BaseEmitter<T> { final AtomicInteger wip; - LatestAsyncEmitter(Subscriber<? super T> actual) { - super(actual); + LatestAsyncEmitter(Subscriber<? super T> downstream) { + super(downstream); this.queue = new AtomicReference<T>(); this.wip = new AtomicInteger(); } @@ -647,7 +651,7 @@ void drain() { } int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final AtomicReference<T> q = queue; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java index 34bc84c4c3..143e61ec55 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java @@ -45,10 +45,10 @@ static final class DebounceSubscriber<T, U> extends AtomicLong implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = 6725975399620862591L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Function<? super T, ? extends Publisher<U>> debounceSelector; - Subscription s; + Subscription upstream; final AtomicReference<Disposable> debouncer = new AtomicReference<Disposable>(); @@ -58,15 +58,15 @@ static final class DebounceSubscriber<T, U> extends AtomicLong DebounceSubscriber(Subscriber<? super T> actual, Function<? super T, ? extends Publisher<U>> debounceSelector) { - this.actual = actual; + this.downstream = actual; this.debounceSelector = debounceSelector; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -92,7 +92,7 @@ public void onNext(T t) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); + downstream.onError(e); return; } @@ -106,7 +106,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { DisposableHelper.dispose(debouncer); - actual.onError(t); + downstream.onError(t); } @Override @@ -119,9 +119,11 @@ public void onComplete() { if (!DisposableHelper.isDisposed(d)) { @SuppressWarnings("unchecked") DebounceInnerSubscriber<T, U> dis = (DebounceInnerSubscriber<T, U>)d; - dis.emit(); + if (dis != null) { + dis.emit(); + } DisposableHelper.dispose(debouncer); - actual.onComplete(); + downstream.onComplete(); } } @@ -134,7 +136,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); DisposableHelper.dispose(debouncer); } @@ -142,11 +144,11 @@ void emit(long idx, T value) { if (idx == index) { long r = get(); if (r != 0L) { - actual.onNext(value); + downstream.onNext(value); BackpressureHelper.produced(this, 1); } else { cancel(); - actual.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java index 3f667dcb09..51d365e849 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java @@ -51,21 +51,21 @@ static final class DebounceTimedSubscriber<T> extends AtomicLong implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -9102637559663639004L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long timeout; final TimeUnit unit; final Scheduler.Worker worker; - Subscription s; + Subscription upstream; - final SequentialDisposable timer = new SequentialDisposable(); + Disposable timer; volatile long index; boolean done; DebounceTimedSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; @@ -73,9 +73,9 @@ static final class DebounceTimedSubscriber<T> extends AtomicLong @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -88,17 +88,15 @@ public void onNext(T t) { long idx = index + 1; index = idx; - Disposable d = timer.get(); + Disposable d = timer; if (d != null) { d.dispose(); } DebounceEmitter<T> de = new DebounceEmitter<T>(t, idx, this); - if (timer.replace(de)) { - d = worker.schedule(de, timeout, unit); - - de.setResource(d); - } + timer = de; + d = worker.schedule(de, timeout, unit); + de.setResource(d); } @Override @@ -108,7 +106,11 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + Disposable d = timer; + if (d != null) { + d.dispose(); + } + downstream.onError(t); worker.dispose(); } @@ -119,17 +121,19 @@ public void onComplete() { } done = true; - Disposable d = timer.get(); - if (!DisposableHelper.isDisposed(d)) { - @SuppressWarnings("unchecked") - DebounceEmitter<T> de = (DebounceEmitter<T>)d; - if (de != null) { - de.emit(); - } - DisposableHelper.dispose(timer); - actual.onComplete(); - worker.dispose(); + Disposable d = timer; + if (d != null) { + d.dispose(); + } + + @SuppressWarnings("unchecked") + DebounceEmitter<T> de = (DebounceEmitter<T>)d; + if (de != null) { + de.emit(); } + + downstream.onComplete(); + worker.dispose(); } @Override @@ -141,7 +145,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); worker.dispose(); } @@ -149,13 +153,13 @@ void emit(long idx, T t, DebounceEmitter<T> emitter) { if (idx == index) { long r = get(); if (r != 0L) { - actual.onNext(t); + downstream.onNext(t); BackpressureHelper.produced(this, 1); emitter.dispose(); } else { cancel(); - actual.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); } } } @@ -171,7 +175,6 @@ static final class DebounceEmitter<T> extends AtomicReference<Disposable> implem final AtomicBoolean once = new AtomicBoolean(); - DebounceEmitter(T value, long idx, DebounceTimedSubscriber<T> parent) { this.value = value; this.idx = idx; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java index 8a44a0019e..7ac266001f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java @@ -27,6 +27,7 @@ public final class FlowableDefer<T> extends Flowable<T> { public FlowableDefer(Callable<? extends Publisher<? extends T>> supplier) { this.supplier = supplier; } + @Override public void subscribeActual(Subscriber<? super T> s) { Publisher<? extends T> pub; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java index 3732c48d63..684c11f549 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java @@ -38,30 +38,30 @@ public FlowableDelay(Flowable<T> source, long delay, TimeUnit unit, Scheduler sc @Override protected void subscribeActual(Subscriber<? super T> t) { - Subscriber<? super T> s; + Subscriber<? super T> downstream; if (delayError) { - s = t; + downstream = t; } else { - s = new SerializedSubscriber<T>(t); + downstream = new SerializedSubscriber<T>(t); } Scheduler.Worker w = scheduler.createWorker(); - source.subscribe(new DelaySubscriber<T>(s, delay, unit, w, delayError)); + source.subscribe(new DelaySubscriber<T>(downstream, delay, unit, w, delayError)); } static final class DelaySubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long delay; final TimeUnit unit; final Scheduler.Worker w; final boolean delayError; - Subscription s; + Subscription upstream; DelaySubscriber(Subscriber<? super T> actual, long delay, TimeUnit unit, Worker w, boolean delayError) { super(); - this.actual = actual; + this.downstream = actual; this.delay = delay; this.unit = unit; this.w = w; @@ -70,9 +70,9 @@ static final class DelaySubscriber<T> implements FlowableSubscriber<T>, Subscrip @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -93,12 +93,12 @@ public void onComplete() { @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); w.dispose(); } @@ -111,7 +111,7 @@ final class OnNext implements Runnable { @Override public void run() { - actual.onNext(t); + downstream.onNext(t); } } @@ -125,7 +125,7 @@ final class OnError implements Runnable { @Override public void run() { try { - actual.onError(t); + downstream.onError(t); } finally { w.dispose(); } @@ -136,7 +136,7 @@ final class OnComplete implements Runnable { @Override public void run() { try { - actual.onComplete(); + downstream.onComplete(); } finally { w.dispose(); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java index 0d0a37db3e..ea52987d59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java @@ -12,10 +12,12 @@ */ package io.reactivex.internal.operators.flowable; +import java.util.concurrent.atomic.*; + import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.internal.subscriptions.SubscriptionArbiter; +import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; /** @@ -35,93 +37,105 @@ public FlowableDelaySubscriptionOther(Publisher<? extends T> main, Publisher<U> @Override public void subscribeActual(final Subscriber<? super T> child) { - final SubscriptionArbiter serial = new SubscriptionArbiter(); - child.onSubscribe(serial); + MainSubscriber<T> parent = new MainSubscriber<T>(child, main); + child.onSubscribe(parent); + other.subscribe(parent.other); + } - FlowableSubscriber<U> otherSubscriber = new DelaySubscriber(serial, child); + static final class MainSubscriber<T> extends AtomicLong implements FlowableSubscriber<T>, Subscription { - other.subscribe(otherSubscriber); - } + private static final long serialVersionUID = 2259811067697317255L; + + final Subscriber<? super T> downstream; + + final Publisher<? extends T> main; + + final OtherSubscriber other; - final class DelaySubscriber implements FlowableSubscriber<U> { - final SubscriptionArbiter serial; - final Subscriber<? super T> child; - boolean done; + final AtomicReference<Subscription> upstream; - DelaySubscriber(SubscriptionArbiter serial, Subscriber<? super T> child) { - this.serial = serial; - this.child = child; + MainSubscriber(Subscriber<? super T> downstream, Publisher<? extends T> main) { + this.downstream = downstream; + this.main = main; + this.other = new OtherSubscriber(); + this.upstream = new AtomicReference<Subscription>(); } - @Override - public void onSubscribe(final Subscription s) { - serial.setSubscription(new DelaySubscription(s)); - s.request(Long.MAX_VALUE); + void next() { + main.subscribe(this); } @Override - public void onNext(U t) { - onComplete(); + public void onNext(T t) { + downstream.onNext(t); } @Override - public void onError(Throwable e) { - if (done) { - RxJavaPlugins.onError(e); - return; - } - done = true; - child.onError(e); + public void onError(Throwable t) { + downstream.onError(t); } @Override public void onComplete() { - if (done) { - return; - } - done = true; - - main.subscribe(new OnCompleteSubscriber()); + downstream.onComplete(); } - final class DelaySubscription implements Subscription { - private final Subscription s; - - DelaySubscription(Subscription s) { - this.s = s; + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + SubscriptionHelper.deferredRequest(upstream, this, n); } + } - @Override - public void request(long n) { - // ignored - } + @Override + public void cancel() { + SubscriptionHelper.cancel(other); + SubscriptionHelper.cancel(upstream); + } - @Override - public void cancel() { - s.cancel(); - } + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, this, s); } - final class OnCompleteSubscriber implements FlowableSubscriber<T> { + final class OtherSubscriber extends AtomicReference<Subscription> implements FlowableSubscriber<Object> { + + private static final long serialVersionUID = -3892798459447644106L; + @Override public void onSubscribe(Subscription s) { - serial.setSubscription(s); + if (SubscriptionHelper.setOnce(this, s)) { + s.request(Long.MAX_VALUE); + } } @Override - public void onNext(T t) { - child.onNext(t); + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + next(); + } } @Override public void onError(Throwable t) { - child.onError(t); + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } } @Override public void onComplete() { - child.onComplete(); + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + next(); + } } } - } +} } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java index 5ce85197c3..5fe5211834 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java @@ -16,56 +16,79 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; -public final class FlowableDematerialize<T> extends AbstractFlowableWithUpstream<Notification<T>, T> { +public final class FlowableDematerialize<T, R> extends AbstractFlowableWithUpstream<T, R> { - public FlowableDematerialize(Flowable<Notification<T>> source) { + final Function<? super T, ? extends Notification<R>> selector; + + public FlowableDematerialize(Flowable<T> source, Function<? super T, ? extends Notification<R>> selector) { super(source); + this.selector = selector; } @Override - protected void subscribeActual(Subscriber<? super T> s) { - source.subscribe(new DematerializeSubscriber<T>(s)); + protected void subscribeActual(Subscriber<? super R> subscriber) { + source.subscribe(new DematerializeSubscriber<T, R>(subscriber, selector)); } - static final class DematerializeSubscriber<T> implements FlowableSubscriber<Notification<T>>, Subscription { - final Subscriber<? super T> actual; + static final class DematerializeSubscriber<T, R> implements FlowableSubscriber<T>, Subscription { + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends Notification<R>> selector; boolean done; - Subscription s; + Subscription upstream; - DematerializeSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + DematerializeSubscriber(Subscriber<? super R> downstream, Function<? super T, ? extends Notification<R>> selector) { + this.downstream = downstream; + this.selector = selector; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override - public void onNext(Notification<T> t) { + public void onNext(T item) { if (done) { - if (t.isOnError()) { - RxJavaPlugins.onError(t.getError()); + if (item instanceof Notification) { + Notification<?> notification = (Notification<?>)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } } return; } - if (t.isOnError()) { - s.cancel(); - onError(t.getError()); + + Notification<R> notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; } - else if (t.isOnComplete()) { - s.cancel(); + if (notification.isOnError()) { + upstream.cancel(); + onError(notification.getError()); + } else if (notification.isOnComplete()) { + upstream.cancel(); onComplete(); } else { - actual.onNext(t.getValue()); + downstream.onNext(notification.getValue()); } } @@ -77,8 +100,9 @@ public void onError(Throwable t) { } done = true; - actual.onError(t); + downstream.onError(t); } + @Override public void onComplete() { if (done) { @@ -86,17 +110,17 @@ public void onComplete() { } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java index de618949d4..7679ee152e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java @@ -32,54 +32,54 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class DetachSubscriber<T> implements FlowableSubscriber<T>, Subscription { - Subscriber<? super T> actual; + Subscriber<? super T> downstream; - Subscription s; + Subscription upstream; - DetachSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + DetachSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - Subscription s = this.s; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asSubscriber(); + Subscription s = this.upstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); s.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - Subscriber<? super T> a = actual; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asSubscriber(); + Subscriber<? super T> a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); a.onError(t); } @Override public void onComplete() { - Subscriber<? super T> a = actual; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asSubscriber(); + Subscriber<? super T> a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); a.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java index 7072dd3e43..4e9bc5e1bf 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java @@ -41,18 +41,18 @@ public FlowableDistinct(Flowable<T> source, Function<? super T, K> keySelector, } @Override - protected void subscribeActual(Subscriber<? super T> observer) { + protected void subscribeActual(Subscriber<? super T> subscriber) { Collection<? super K> collection; try { collection = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptySubscription.error(ex, observer); + EmptySubscription.error(ex, subscriber); return; } - source.subscribe(new DistinctSubscriber<T, K>(observer, keySelector, collection)); + source.subscribe(new DistinctSubscriber<T, K>(subscriber, keySelector, collection)); } static final class DistinctSubscriber<T, K> extends BasicFuseableSubscriber<T, T> { @@ -85,12 +85,12 @@ public void onNext(T value) { } if (b) { - actual.onNext(value); + downstream.onNext(value); } else { - s.request(1); + upstream.request(1); } } else { - actual.onNext(null); + downstream.onNext(null); } } @@ -101,7 +101,7 @@ public void onError(Throwable e) { } else { done = true; collection.clear(); - actual.onError(e); + downstream.onError(e); } } @@ -110,7 +110,7 @@ public void onComplete() { if (!done) { done = true; collection.clear(); - actual.onComplete(); + downstream.onComplete(); } } @@ -129,7 +129,7 @@ public T poll() throws Exception { return v; } else { if (sourceMode == QueueFuseable.ASYNC) { - s.request(1); + upstream.request(1); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java index 9b1944977b..c1cf54658e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java @@ -46,7 +46,6 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class DistinctUntilChangedSubscriber<T, K> extends BasicFuseableSubscriber<T, T> implements ConditionalSubscriber<T> { - final Function<? super T, K> keySelector; final BiPredicate<? super K, ? super K> comparer; @@ -66,7 +65,7 @@ static final class DistinctUntilChangedSubscriber<T, K> extends BasicFuseableSub @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.request(1); + upstream.request(1); } } @@ -76,7 +75,7 @@ public boolean tryOnNext(T t) { return false; } if (sourceMode != NONE) { - actual.onNext(t); + downstream.onNext(t); return true; } @@ -99,7 +98,7 @@ public boolean tryOnNext(T t) { return true; } - actual.onNext(t); + downstream.onNext(t); return true; } @@ -129,7 +128,7 @@ public T poll() throws Exception { } last = key; if (sourceMode != SYNC) { - s.request(1); + upstream.request(1); } } } @@ -157,7 +156,7 @@ static final class DistinctUntilChangedConditionalSubscriber<T, K> extends Basic @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.request(1); + upstream.request(1); } } @@ -167,7 +166,7 @@ public boolean tryOnNext(T t) { return false; } if (sourceMode != NONE) { - return actual.tryOnNext(t); + return downstream.tryOnNext(t); } K key; @@ -189,7 +188,7 @@ public boolean tryOnNext(T t) { return true; } - actual.onNext(t); + downstream.onNext(t); return true; } @@ -219,7 +218,7 @@ public T poll() throws Exception { } last = key; if (sourceMode != SYNC) { - s.request(1); + upstream.request(1); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java index 039dd86311..968c589e2c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java @@ -23,10 +23,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class FlowableDoAfterNext<T> extends AbstractFlowableWithUpstream<T, T> { final Consumer<? super T> onAfterNext; @@ -59,7 +59,7 @@ public void onNext(T t) { if (done) { return; } - actual.onNext(t); + downstream.onNext(t); if (sourceMode == NONE) { try { @@ -97,7 +97,7 @@ static final class DoAfterConditionalSubscriber<T> extends BasicFuseableConditio @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); if (sourceMode == NONE) { try { @@ -110,7 +110,7 @@ public void onNext(T t) { @Override public boolean tryOnNext(T t) { - boolean b = actual.tryOnNext(t); + boolean b = downstream.tryOnNext(t); try { onAfterNext.accept(t); } catch (Throwable ex) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java index e15faff21a..023c55bf7f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java @@ -25,11 +25,10 @@ /** * Execute an action after an onError, onComplete or a cancel event. - * + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class FlowableDoFinally<T> extends AbstractFlowableWithUpstream<T, T> { final Action onFinally; @@ -52,60 +51,60 @@ static final class DoFinallySubscriber<T> extends BasicIntQueueSubscription<T> i private static final long serialVersionUID = 4109457741734051389L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Action onFinally; - Subscription s; + Subscription upstream; QueueSubscription<T> qs; boolean syncFused; DoFinallySubscriber(Subscriber<? super T> actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @SuppressWarnings("unchecked") @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { this.qs = (QueueSubscription<T>)s; } - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); runFinally(); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); runFinally(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override @@ -157,65 +156,65 @@ static final class DoFinallyConditionalSubscriber<T> extends BasicIntQueueSubscr private static final long serialVersionUID = 4109457741734051389L; - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; final Action onFinally; - Subscription s; + Subscription upstream; QueueSubscription<T> qs; boolean syncFused; DoFinallyConditionalSubscriber(ConditionalSubscriber<? super T> actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @SuppressWarnings("unchecked") @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { this.qs = (QueueSubscription<T>)s; } - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public boolean tryOnNext(T t) { - return actual.tryOnNext(t); + return downstream.tryOnNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); runFinally(); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); runFinally(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java index 2d7d49ca46..e913675f69 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java @@ -78,7 +78,7 @@ public void onNext(T t) { } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return; } @@ -89,7 +89,7 @@ public void onNext(T t) { return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -104,11 +104,11 @@ public void onError(Throwable t) { onError.accept(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); relay = false; } if (relay) { - actual.onError(t); + downstream.onError(t); } try { @@ -132,7 +132,7 @@ public void onComplete() { } done = true; - actual.onComplete(); + downstream.onComplete(); try { onAfterTerminate.run(); @@ -217,7 +217,7 @@ public void onNext(T t) { } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return; } @@ -228,7 +228,7 @@ public void onNext(T t) { return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -244,7 +244,7 @@ public boolean tryOnNext(T t) { return false; } - return actual.tryOnNext(t); + return downstream.tryOnNext(t); } @Override @@ -259,11 +259,11 @@ public void onError(Throwable t) { onError.accept(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); relay = false; } if (relay) { - actual.onError(t); + downstream.onError(t); } try { @@ -287,7 +287,7 @@ public void onComplete() { } done = true; - actual.onComplete(); + downstream.onComplete(); try { onAfterTerminate.run(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java index e68f1a01e2..0c979d2918 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java @@ -39,18 +39,18 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class SubscriptionLambdaSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Consumer<? super Subscription> onSubscribe; final LongConsumer onRequest; final Action onCancel; - Subscription s; + Subscription upstream; SubscriptionLambdaSubscriber(Subscriber<? super T> actual, Consumer<? super Subscription> onSubscribe, LongConsumer onRequest, Action onCancel) { - this.actual = actual; + this.downstream = actual; this.onSubscribe = onSubscribe; this.onCancel = onCancel; this.onRequest = onRequest; @@ -64,25 +64,25 @@ public void onSubscribe(Subscription s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); s.cancel(); - this.s = SubscriptionHelper.CANCELLED; - EmptySubscription.error(e, actual); + this.upstream = SubscriptionHelper.CANCELLED; + EmptySubscription.error(e, downstream); return; } - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - if (s != SubscriptionHelper.CANCELLED) { - actual.onError(t); + if (upstream != SubscriptionHelper.CANCELLED) { + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -90,8 +90,8 @@ public void onError(Throwable t) { @Override public void onComplete() { - if (s != SubscriptionHelper.CANCELLED) { - actual.onComplete(); + if (upstream != SubscriptionHelper.CANCELLED) { + downstream.onComplete(); } } @@ -103,18 +103,22 @@ public void request(long n) { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(e); } - s.request(n); + upstream.request(n); } @Override public void cancel() { - try { - onCancel.run(); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - RxJavaPlugins.onError(e); + Subscription s = upstream; + if (s != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; + try { + onCancel.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + s.cancel(); } - s.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java index 261cbd88cf..9d3ead4a46 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java @@ -46,7 +46,7 @@ static final class ElementAtSubscriber<T> extends DeferredScalarSubscription<T> final T defaultValue; final boolean errorOnFewer; - Subscription s; + Subscription upstream; long count; @@ -61,9 +61,9 @@ static final class ElementAtSubscriber<T> extends DeferredScalarSubscription<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -76,7 +76,7 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.cancel(); + upstream.cancel(); complete(t); return; } @@ -90,7 +90,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -100,9 +100,9 @@ public void onComplete() { T v = defaultValue; if (v == null) { if (errorOnFewer) { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } else { - actual.onComplete(); + downstream.onComplete(); } } else { complete(v); @@ -113,7 +113,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java index d8907aa310..4d411990ac 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java @@ -32,8 +32,8 @@ public FlowableElementAtMaybe(Flowable<T> source, long index) { } @Override - protected void subscribeActual(MaybeObserver<? super T> s) { - source.subscribe(new ElementAtSubscriber<T>(s, index)); + protected void subscribeActual(MaybeObserver<? super T> observer) { + source.subscribe(new ElementAtSubscriber<T>(observer, index)); } @Override @@ -43,26 +43,26 @@ public Flowable<T> fuseToFlowable() { static final class ElementAtSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final long index; - Subscription s; + Subscription upstream; long count; boolean done; ElementAtSubscriber(MaybeObserver<? super T> actual, long index) { - this.actual = actual; + this.downstream = actual; this.index = index; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -75,9 +75,9 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(t); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(t); return; } count = c + 1; @@ -90,28 +90,28 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java index 874f744010..7cd542d497 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java @@ -37,8 +37,8 @@ public FlowableElementAtSingle(Flowable<T> source, long index, T defaultValue) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - source.subscribe(new ElementAtSubscriber<T>(s, index, defaultValue)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new ElementAtSubscriber<T>(observer, index, defaultValue)); } @Override @@ -48,28 +48,28 @@ public Flowable<T> fuseToFlowable() { static final class ElementAtSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final long index; final T defaultValue; - Subscription s; + Subscription upstream; long count; boolean done; ElementAtSubscriber(SingleObserver<? super T> actual, long index, T defaultValue) { - this.actual = actual; + this.downstream = actual; this.index = index; this.defaultValue = defaultValue; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -82,9 +82,9 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(t); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(t); return; } count = c + 1; @@ -97,35 +97,35 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; if (!done) { done = true; T v = defaultValue; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java index b74ad1d871..dc88f01d7e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java @@ -27,6 +27,7 @@ public final class FlowableError<T> extends Flowable<T> { public FlowableError(Callable<? extends Throwable> errorSupplier) { this.errorSupplier = errorSupplier; } + @Override public void subscribeActual(Subscriber<? super T> s) { Throwable error; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java index 96b529f3f2..dd90b2c1ec 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java @@ -50,7 +50,7 @@ static final class FilterSubscriber<T> extends BasicFuseableSubscriber<T, T> @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.request(1); + upstream.request(1); } } @@ -60,7 +60,7 @@ public boolean tryOnNext(T t) { return false; } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return true; } boolean b; @@ -71,7 +71,7 @@ public boolean tryOnNext(T t) { return true; } if (b) { - actual.onNext(t); + downstream.onNext(t); } return b; } @@ -102,8 +102,6 @@ public T poll() throws Exception { } } } - - } static final class FilterConditionalSubscriber<T> extends BasicFuseableConditionalSubscriber<T, T> { @@ -117,7 +115,7 @@ static final class FilterConditionalSubscriber<T> extends BasicFuseableCondition @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.request(1); + upstream.request(1); } } @@ -128,7 +126,7 @@ public boolean tryOnNext(T t) { } if (sourceMode != NONE) { - return actual.tryOnNext(null); + return downstream.tryOnNext(null); } boolean b; @@ -138,7 +136,7 @@ public boolean tryOnNext(T t) { fail(e); return true; } - return b && actual.tryOnNext(t); + return b && downstream.tryOnNext(t); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java index 9ffd7df2f2..a7631de8b8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java @@ -63,7 +63,7 @@ static final class MergeSubscriber<T, U> extends AtomicInteger implements Flowab private static final long serialVersionUID = -2117620485640801370L; - final Subscriber<? super U> actual; + final Subscriber<? super U> downstream; final Function<? super T, ? extends Publisher<? extends U>> mapper; final boolean delayErrors; final int maxConcurrency; @@ -85,7 +85,7 @@ static final class MergeSubscriber<T, U> extends AtomicInteger implements Flowab final AtomicLong requested = new AtomicLong(); - Subscription s; + Subscription upstream; long uniqueId; long lastId; @@ -96,7 +96,7 @@ static final class MergeSubscriber<T, U> extends AtomicInteger implements Flowab MergeSubscriber(Subscriber<? super U> actual, Function<? super T, ? extends Publisher<? extends U>> mapper, boolean delayErrors, int maxConcurrency, int bufferSize) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.maxConcurrency = maxConcurrency; @@ -107,9 +107,9 @@ static final class MergeSubscriber<T, U> extends AtomicInteger implements Flowab @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); if (!cancelled) { if (maxConcurrency == Integer.MAX_VALUE) { s.request(Long.MAX_VALUE); @@ -132,7 +132,7 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } @@ -154,7 +154,7 @@ public void onNext(T t) { if (maxConcurrency != Integer.MAX_VALUE && !cancelled && ++scalarEmitted == scalarLimit) { scalarEmitted = 0; - s.request(scalarLimit); + upstream.request(scalarLimit); } } } else { @@ -185,10 +185,10 @@ boolean addInner(InnerSubscriber<T, U> inner) { void removeInner(InnerSubscriber<T, U> inner) { for (;;) { InnerSubscriber<?, ?>[] a = subscribers.get(); - if (a == CANCELLED || a == EMPTY) { + int n = a.length; + if (n == 0) { return; } - int n = a.length; int j = -1; for (int i = 0; i < n; i++) { if (a[i] == inner) { @@ -231,14 +231,14 @@ void tryEmitScalar(U value) { long r = requested.get(); SimpleQueue<U> q = queue; if (r != 0L && (q == null || q.isEmpty())) { - actual.onNext(value); + downstream.onNext(value); if (r != Long.MAX_VALUE) { requested.decrementAndGet(); } if (maxConcurrency != Integer.MAX_VALUE && !cancelled && ++scalarEmitted == scalarLimit) { scalarEmitted = 0; - s.request(scalarLimit); + upstream.request(scalarLimit); } } else { if (q == null) { @@ -279,7 +279,7 @@ void tryEmit(U value, InnerSubscriber<T, U> inner) { long r = requested.get(); SimpleQueue<U> q = inner.queue; if (r != 0L && (q == null || q.isEmpty())) { - actual.onNext(value); + downstream.onNext(value); if (r != Long.MAX_VALUE) { requested.decrementAndGet(); } @@ -322,6 +322,11 @@ public void onError(Throwable t) { } if (errs.addThrowable(t)) { done = true; + if (!delayErrors) { + for (InnerSubscriber<?, ?> a : subscribers.getAndSet(CANCELLED)) { + a.dispose(); + } + } drain(); } else { RxJavaPlugins.onError(t); @@ -350,7 +355,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); disposeAll(); if (getAndIncrement() == 0) { SimpleQueue<U> q = queue; @@ -368,7 +373,7 @@ void drain() { } void drainLoop() { - final Subscriber<? super U> child = this.actual; + final Subscriber<? super U> child = this.downstream; int missed = 1; for (;;) { if (checkTerminate()) { @@ -461,6 +466,7 @@ void drainLoop() { if (checkTerminate()) { return; } + @SuppressWarnings("unchecked") InnerSubscriber<T, U> is = (InnerSubscriber<T, U>)inner[j]; @@ -482,6 +488,9 @@ void drainLoop() { Exceptions.throwIfFatal(ex); is.dispose(); errs.addThrowable(ex); + if (!delayErrors) { + upstream.cancel(); + } if (checkTerminate()) { return; } @@ -539,7 +548,7 @@ void drainLoop() { } if (replenishMain != 0L && !cancelled) { - s.request(replenishMain); + upstream.request(replenishMain); } if (innerCompleted) { continue; @@ -560,7 +569,7 @@ boolean checkTerminate() { clearScalarQueue(); Throwable ex = errs.terminate(); if (ex != ExceptionHelper.TERMINATED) { - actual.onError(ex); + downstream.onError(ex); } return true; } @@ -594,7 +603,7 @@ void innerError(InnerSubscriber<T, U> inner, Throwable t) { if (errs.addThrowable(t)) { inner.done = true; if (!delayErrors) { - s.cancel(); + upstream.cancel(); for (InnerSubscriber<?, ?> a : subscribers.getAndSet(CANCELLED)) { a.dispose(); } @@ -626,6 +635,7 @@ static final class InnerSubscriber<T, U> extends AtomicReference<Subscription> this.bufferSize = parent.bufferSize; this.limit = bufferSize >> 2; } + @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.setOnce(this, s)) { @@ -651,6 +661,7 @@ public void onSubscribe(Subscription s) { s.request(bufferSize); } } + @Override public void onNext(U t) { if (fusionMode != QueueSubscription.ASYNC) { @@ -659,11 +670,13 @@ public void onNext(U t) { parent.drain(); } } + @Override public void onError(Throwable t) { lazySet(SubscriptionHelper.CANCELLED); parent.innerError(this, t); } + @Override public void onComplete() { done = true; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java index c1f0a6c1e9..86311bc27d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java @@ -50,15 +50,15 @@ public FlowableFlatMapCompletable(Flowable<T> source, } @Override - protected void subscribeActual(Subscriber<? super T> observer) { - source.subscribe(new FlatMapCompletableMainSubscriber<T>(observer, mapper, delayErrors, maxConcurrency)); + protected void subscribeActual(Subscriber<? super T> subscriber) { + source.subscribe(new FlatMapCompletableMainSubscriber<T>(subscriber, mapper, delayErrors, maxConcurrency)); } static final class FlatMapCompletableMainSubscriber<T> extends BasicIntQueueSubscription<T> implements FlowableSubscriber<T> { private static final long serialVersionUID = 8443155186132538303L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicThrowable errors; @@ -70,14 +70,14 @@ static final class FlatMapCompletableMainSubscriber<T> extends BasicIntQueueSubs final int maxConcurrency; - Subscription s; + Subscription upstream; volatile boolean cancelled; - FlatMapCompletableMainSubscriber(Subscriber<? super T> observer, + FlatMapCompletableMainSubscriber(Subscriber<? super T> subscriber, Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors, int maxConcurrency) { - this.actual = observer; + this.downstream = subscriber; this.mapper = mapper; this.delayErrors = delayErrors; this.errors = new AtomicThrowable(); @@ -88,10 +88,10 @@ static final class FlatMapCompletableMainSubscriber<T> extends BasicIntQueueSubs @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); int m = maxConcurrency; if (m == Integer.MAX_VALUE) { @@ -110,7 +110,7 @@ public void onNext(T value) { cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -130,17 +130,17 @@ public void onError(Throwable e) { if (delayErrors) { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } else { cancel(); if (getAndSet(0) > 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } } else { @@ -153,13 +153,13 @@ public void onComplete() { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } @@ -167,7 +167,7 @@ public void onComplete() { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); set.dispose(); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java index 16f126b363..ad5434273b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java @@ -65,7 +65,7 @@ static final class FlatMapCompletableMainSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Disposable { private static final long serialVersionUID = 8443155186132538303L; - final CompletableObserver actual; + final CompletableObserver downstream; final AtomicThrowable errors; @@ -77,14 +77,14 @@ static final class FlatMapCompletableMainSubscriber<T> extends AtomicInteger final int maxConcurrency; - Subscription s; + Subscription upstream; volatile boolean disposed; FlatMapCompletableMainSubscriber(CompletableObserver observer, Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors, int maxConcurrency) { - this.actual = observer; + this.downstream = observer; this.mapper = mapper; this.delayErrors = delayErrors; this.errors = new AtomicThrowable(); @@ -95,10 +95,10 @@ static final class FlatMapCompletableMainSubscriber<T> extends AtomicInteger @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); int m = maxConcurrency; if (m == Integer.MAX_VALUE) { @@ -117,7 +117,7 @@ public void onNext(T value) { cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -137,17 +137,17 @@ public void onError(Throwable e) { if (delayErrors) { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } else { dispose(); if (getAndSet(0) > 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } } else { @@ -160,13 +160,13 @@ public void onComplete() { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } } @@ -174,7 +174,7 @@ public void onComplete() { @Override public void dispose() { disposed = true; - s.cancel(); + upstream.cancel(); set.dispose(); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java index 8b27fe702a..ab2950e2a3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java @@ -60,7 +60,7 @@ static final class FlatMapMaybeSubscriber<T, R> private static final long serialVersionUID = 8600231336733376951L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final boolean delayErrors; @@ -78,13 +78,13 @@ static final class FlatMapMaybeSubscriber<T, R> final AtomicReference<SpscLinkedArrayQueue<R>> queue; - Subscription s; + Subscription upstream; volatile boolean cancelled; FlatMapMaybeSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean delayErrors, int maxConcurrency) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.maxConcurrency = maxConcurrency; @@ -97,10 +97,10 @@ static final class FlatMapMaybeSubscriber<T, R> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); int m = maxConcurrency; if (m == Integer.MAX_VALUE) { @@ -119,7 +119,7 @@ public void onNext(T t) { ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -155,7 +155,7 @@ public void onComplete() { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); set.dispose(); } @@ -172,22 +172,22 @@ void innerSuccess(InnerObserver inner, R value) { if (get() == 0 && compareAndSet(0, 1)) { boolean d = active.decrementAndGet() == 0; if (requested.get() != 0) { - actual.onNext(value); + downstream.onNext(value); SpscLinkedArrayQueue<R> q = queue.get(); if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } BackpressureHelper.produced(requested, 1); if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } else { SpscLinkedArrayQueue<R> q = getOrCreateQueue(); @@ -228,11 +228,11 @@ void innerError(InnerObserver inner, Throwable e) { set.delete(inner); if (errors.addThrowable(e)) { if (!delayErrors) { - s.cancel(); + upstream.cancel(); set.dispose(); } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } active.decrementAndGet(); @@ -252,15 +252,15 @@ void innerComplete(InnerObserver inner) { if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } if (decrementAndGet() == 0) { return; @@ -269,7 +269,7 @@ void innerComplete(InnerObserver inner) { } else { active.decrementAndGet(); if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } drain(); } @@ -290,7 +290,7 @@ void clear() { void drainLoop() { int missed = 1; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; AtomicInteger n = active; AtomicReference<SpscLinkedArrayQueue<R>> qr = queue; @@ -372,7 +372,7 @@ void drainLoop() { if (e != 0L) { BackpressureHelper.produced(requested, e); if (maxConcurrency != Integer.MAX_VALUE) { - s.request(e); + upstream.request(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java index 0b6c4bc9be..0633248d8c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java @@ -60,7 +60,7 @@ static final class FlatMapSingleSubscriber<T, R> private static final long serialVersionUID = 8600231336733376951L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final boolean delayErrors; @@ -78,13 +78,13 @@ static final class FlatMapSingleSubscriber<T, R> final AtomicReference<SpscLinkedArrayQueue<R>> queue; - Subscription s; + Subscription upstream; volatile boolean cancelled; FlatMapSingleSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean delayErrors, int maxConcurrency) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.maxConcurrency = maxConcurrency; @@ -97,10 +97,10 @@ static final class FlatMapSingleSubscriber<T, R> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); int m = maxConcurrency; if (m == Integer.MAX_VALUE) { @@ -119,7 +119,7 @@ public void onNext(T t) { ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -155,7 +155,7 @@ public void onComplete() { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); set.dispose(); } @@ -172,22 +172,22 @@ void innerSuccess(InnerObserver inner, R value) { if (get() == 0 && compareAndSet(0, 1)) { boolean d = active.decrementAndGet() == 0; if (requested.get() != 0) { - actual.onNext(value); + downstream.onNext(value); SpscLinkedArrayQueue<R> q = queue.get(); if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } BackpressureHelper.produced(requested, 1); if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } else { SpscLinkedArrayQueue<R> q = getOrCreateQueue(); @@ -228,11 +228,11 @@ void innerError(InnerObserver inner, Throwable e) { set.delete(inner); if (errors.addThrowable(e)) { if (!delayErrors) { - s.cancel(); + upstream.cancel(); set.dispose(); } else { if (maxConcurrency != Integer.MAX_VALUE) { - s.request(1); + upstream.request(1); } } active.decrementAndGet(); @@ -257,7 +257,7 @@ void clear() { void drainLoop() { int missed = 1; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; AtomicInteger n = active; AtomicReference<SpscLinkedArrayQueue<R>> qr = queue; @@ -339,7 +339,7 @@ void drainLoop() { if (e != 0L) { BackpressureHelper.produced(requested, e); if (maxConcurrency != Integer.MAX_VALUE) { - s.request(e); + upstream.request(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java index 48a9d75333..01398b9a29 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java @@ -85,10 +85,9 @@ static final class FlattenIterableSubscriber<T, R> extends BasicIntQueueSubscription<R> implements FlowableSubscriber<T> { - private static final long serialVersionUID = -3096000382929934955L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; @@ -98,7 +97,7 @@ static final class FlattenIterableSubscriber<T, R> final AtomicLong requested; - Subscription s; + Subscription upstream; SimpleQueue<T> queue; @@ -116,7 +115,7 @@ static final class FlattenIterableSubscriber<T, R> FlattenIterableSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper, int prefetch) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.prefetch = prefetch; this.limit = prefetch - (prefetch >> 2); @@ -126,8 +125,8 @@ static final class FlattenIterableSubscriber<T, R> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") @@ -140,7 +139,7 @@ public void onSubscribe(Subscription s) { this.queue = qs; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } @@ -148,7 +147,7 @@ public void onSubscribe(Subscription s) { fusionMode = m; this.queue = qs; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); return; @@ -157,7 +156,7 @@ public void onSubscribe(Subscription s) { queue = new SpscArrayQueue<T>(prefetch); - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); } @@ -207,7 +206,7 @@ public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { queue.clear(); @@ -220,7 +219,7 @@ void drain() { return; } - final Subscriber<? super R> a = actual; + final Subscriber<? super R> a = downstream; final SimpleQueue<T> q = queue; final boolean replenish = fusionMode != SYNC; @@ -240,7 +239,7 @@ void drain() { t = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); ExceptionHelper.addThrowable(error, ex); ex = ExceptionHelper.terminate(error); @@ -270,7 +269,7 @@ void drain() { b = it.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); ExceptionHelper.addThrowable(error, ex); ex = ExceptionHelper.terminate(error); a.onError(ex); @@ -303,7 +302,7 @@ void drain() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); current = null; - s.cancel(); + upstream.cancel(); ExceptionHelper.addThrowable(error, ex); ex = ExceptionHelper.terminate(error); a.onError(ex); @@ -325,7 +324,7 @@ void drain() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); current = null; - s.cancel(); + upstream.cancel(); ExceptionHelper.addThrowable(error, ex); ex = ExceptionHelper.terminate(error); a.onError(ex); @@ -372,7 +371,7 @@ void consumedOne(boolean enabled) { int c = consumed + 1; if (c == limit) { consumed = 0; - s.request(c); + upstream.request(c); } else { consumed = c; } @@ -411,11 +410,7 @@ public void clear() { @Override public boolean isEmpty() { - Iterator<? extends R> it = current; - if (it == null) { - return queue.isEmpty(); - } - return !it.hasNext(); + return current == null && queue.isEmpty(); } @Nullable diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java index b6819d06d5..d54fb15a21 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java @@ -28,6 +28,7 @@ public final class FlowableFromArray<T> extends Flowable<T> { public FlowableFromArray(T[] array) { this.array = array; } + @Override public void subscribeActual(Subscriber<? super T> s) { if (s instanceof ConditionalSubscriber) { @@ -92,13 +93,11 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -106,21 +105,20 @@ public final void cancel() { static final class ArraySubscription<T> extends BaseArraySubscription<T> { - private static final long serialVersionUID = 2587302975077663557L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; ArraySubscription(Subscriber<? super T> actual, T[] array) { super(array); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { T[] arr = array; int f = arr.length; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; for (int i = index; i != f; i++) { if (cancelled) { @@ -128,7 +126,7 @@ void fastPath() { } T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.onNext(t); @@ -146,7 +144,7 @@ void slowPath(long r) { T[] arr = array; int f = arr.length; int i = index; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; for (;;) { @@ -158,7 +156,7 @@ void slowPath(long r) { T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.onNext(t); @@ -190,21 +188,20 @@ void slowPath(long r) { static final class ArrayConditionalSubscription<T> extends BaseArraySubscription<T> { - private static final long serialVersionUID = 2587302975077663557L; - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; ArrayConditionalSubscription(ConditionalSubscriber<? super T> actual, T[] array) { super(array); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { T[] arr = array; int f = arr.length; - ConditionalSubscriber<? super T> a = actual; + ConditionalSubscriber<? super T> a = downstream; for (int i = index; i != f; i++) { if (cancelled) { @@ -212,7 +209,7 @@ void fastPath() { } T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.tryOnNext(t); @@ -230,7 +227,7 @@ void slowPath(long r) { T[] arr = array; int f = arr.length; int i = index; - ConditionalSubscriber<? super T> a = actual; + ConditionalSubscriber<? super T> a = downstream; for (;;) { @@ -242,7 +239,7 @@ void slowPath(long r) { T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { if (a.tryOnNext(t)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java index 4920c9f206..6dcb226daa 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java @@ -21,12 +21,14 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.DeferredScalarSubscription; +import io.reactivex.plugins.RxJavaPlugins; public final class FlowableFromCallable<T> extends Flowable<T> implements Callable<T> { final Callable<? extends T> callable; public FlowableFromCallable(Callable<? extends T> callable) { this.callable = callable; } + @Override public void subscribeActual(Subscriber<? super T> s) { DeferredScalarSubscription<T> deferred = new DeferredScalarSubscription<T>(s); @@ -37,7 +39,11 @@ public void subscribeActual(Subscriber<? super T> s) { t = ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(ex); + if (deferred.isCancelled()) { + RxJavaPlugins.onError(ex); + } else { + s.onError(ex); + } return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java index ac1b0238dc..e893dca7e8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java @@ -104,7 +104,6 @@ public final T poll() { return ObjectHelper.requireNonNull(it.next(), "Iterator.next() returned a null value"); } - @Override public final boolean isEmpty() { return it == null || !it.hasNext(); @@ -128,7 +127,6 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; @@ -141,20 +139,19 @@ public final void cancel() { static final class IteratorSubscription<T> extends BaseRangeSubscription<T> { - private static final long serialVersionUID = -6022804456014692607L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; IteratorSubscription(Subscriber<? super T> actual, Iterator<? extends T> it) { super(it); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { Iterator<? extends T> it = this.it; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; for (;;) { if (cancelled) { return; @@ -195,7 +192,6 @@ void fastPath() { return; } - if (!b) { if (!cancelled) { a.onComplete(); @@ -209,7 +205,7 @@ void fastPath() { void slowPath(long r) { long e = 0L; Iterator<? extends T> it = this.it; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; for (;;) { @@ -279,20 +275,19 @@ void slowPath(long r) { static final class IteratorConditionalSubscription<T> extends BaseRangeSubscription<T> { - private static final long serialVersionUID = -6022804456014692607L; - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; IteratorConditionalSubscription(ConditionalSubscriber<? super T> actual, Iterator<? extends T> it) { super(it); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { Iterator<? extends T> it = this.it; - ConditionalSubscriber<? super T> a = actual; + ConditionalSubscriber<? super T> a = downstream; for (;;) { if (cancelled) { return; @@ -346,7 +341,7 @@ void fastPath() { void slowPath(long r) { long e = 0L; Iterator<? extends T> it = this.it; - ConditionalSubscriber<? super T> a = actual; + ConditionalSubscriber<? super T> a = downstream; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java index 7b24ab0ce1..7ad4edb269 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java @@ -29,37 +29,39 @@ protected void subscribeActual(Subscriber<? super T> s) { upstream.subscribe(new SubscriberObserver<T>(s)); } - static class SubscriberObserver<T> implements Observer<T>, Subscription { - private final Subscriber<? super T> s; - private Disposable d; + static final class SubscriberObserver<T> implements Observer<T>, Subscription { + + final Subscriber<? super T> downstream; + + Disposable upstream; SubscriberObserver(Subscriber<? super T> s) { - this.s = s; + this.downstream = s; } @Override public void onComplete() { - s.onComplete(); + downstream.onComplete(); } @Override public void onError(Throwable e) { - s.onError(e); + downstream.onError(e); } @Override public void onNext(T value) { - s.onNext(value); + downstream.onNext(value); } @Override public void onSubscribe(Disposable d) { - this.d = d; - s.onSubscribe(this); + this.upstream = d; + downstream.onSubscribe(this); } @Override public void cancel() { - d.dispose(); + upstream.dispose(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java index 54a44151f3..17bdd790b9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java @@ -58,7 +58,7 @@ static final class GeneratorSubscription<T, S> private static final long serialVersionUID = 7565982551505011832L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final BiFunction<S, ? super Emitter<T>, S> generator; final Consumer<? super S> disposeState; @@ -73,7 +73,7 @@ static final class GeneratorSubscription<T, S> GeneratorSubscription(Subscriber<? super T> actual, BiFunction<S, ? super Emitter<T>, S> generator, Consumer<? super S> disposeState, S initialState) { - this.actual = actual; + this.downstream = actual; this.generator = generator; this.disposeState = disposeState; this.state = initialState; @@ -171,7 +171,7 @@ public void onNext(T t) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); } else { hasNext = true; - actual.onNext(t); + downstream.onNext(t); } } } @@ -186,7 +186,7 @@ public void onError(Throwable t) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } terminate = true; - actual.onError(t); + downstream.onError(t); } } @@ -194,7 +194,7 @@ public void onError(Throwable t) { public void onComplete() { if (!terminate) { terminate = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java index a89a47ff72..99a63c7b91 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java @@ -14,7 +14,9 @@ package io.reactivex.internal.operators.flowable; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.*; import org.reactivestreams.*; @@ -23,11 +25,13 @@ import io.reactivex.annotations.Nullable; import io.reactivex.exceptions.Exceptions; import io.reactivex.flowables.GroupedFlowable; +import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.queue.SpscLinkedArrayQueue; import io.reactivex.internal.subscriptions.*; import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.internal.util.EmptyComponent; import io.reactivex.plugins.RxJavaPlugins; public final class FlowableGroupBy<T, K, V> extends AbstractFlowableWithUpstream<T, GroupedFlowable<K, V>> { @@ -35,18 +39,43 @@ public final class FlowableGroupBy<T, K, V> extends AbstractFlowableWithUpstream final Function<? super T, ? extends V> valueSelector; final int bufferSize; final boolean delayError; + final Function<? super Consumer<Object>, ? extends Map<K, Object>> mapFactory; - public FlowableGroupBy(Flowable<T> source, Function<? super T, ? extends K> keySelector, Function<? super T, ? extends V> valueSelector, int bufferSize, boolean delayError) { + public FlowableGroupBy(Flowable<T> source, Function<? super T, ? extends K> keySelector, Function<? super T, ? extends V> valueSelector, + int bufferSize, boolean delayError, Function<? super Consumer<Object>, ? extends Map<K, Object>> mapFactory) { super(source); this.keySelector = keySelector; this.valueSelector = valueSelector; this.bufferSize = bufferSize; this.delayError = delayError; + this.mapFactory = mapFactory; } @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) protected void subscribeActual(Subscriber<? super GroupedFlowable<K, V>> s) { - source.subscribe(new GroupBySubscriber<T, K, V>(s, keySelector, valueSelector, bufferSize, delayError)); + + final Map<Object, GroupedUnicast<K, V>> groups; + final Queue<GroupedUnicast<K, V>> evictedGroups; + + try { + if (mapFactory == null) { + evictedGroups = null; + groups = new ConcurrentHashMap<Object, GroupedUnicast<K, V>>(); + } else { + evictedGroups = new ConcurrentLinkedQueue<GroupedUnicast<K, V>>(); + Consumer<Object> evictionAction = (Consumer) new EvictionAction<K, V>(evictedGroups); + groups = (Map) mapFactory.apply(evictionAction); + } + } catch (Exception e) { + Exceptions.throwIfFatal(e); + s.onSubscribe(EmptyComponent.INSTANCE); + s.onError(e); + return; + } + GroupBySubscriber<T, K, V> subscriber = + new GroupBySubscriber<T, K, V>(s, keySelector, valueSelector, bufferSize, delayError, groups, evictedGroups); + source.subscribe(subscriber); } public static final class GroupBySubscriber<T, K, V> @@ -55,17 +84,18 @@ public static final class GroupBySubscriber<T, K, V> private static final long serialVersionUID = -3688291656102519502L; - final Subscriber<? super GroupedFlowable<K, V>> actual; + final Subscriber<? super GroupedFlowable<K, V>> downstream; final Function<? super T, ? extends K> keySelector; final Function<? super T, ? extends V> valueSelector; final int bufferSize; final boolean delayError; final Map<Object, GroupedUnicast<K, V>> groups; final SpscLinkedArrayQueue<GroupedFlowable<K, V>> queue; + final Queue<GroupedUnicast<K, V>> evictedGroups; static final Object NULL_KEY = new Object(); - Subscription s; + Subscription upstream; final AtomicBoolean cancelled = new AtomicBoolean(); @@ -74,25 +104,29 @@ public static final class GroupBySubscriber<T, K, V> final AtomicInteger groupCount = new AtomicInteger(1); Throwable error; - volatile boolean done; + volatile boolean finished; + boolean done; boolean outputFused; - public GroupBySubscriber(Subscriber<? super GroupedFlowable<K, V>> actual, Function<? super T, ? extends K> keySelector, Function<? super T, ? extends V> valueSelector, int bufferSize, boolean delayError) { - this.actual = actual; + public GroupBySubscriber(Subscriber<? super GroupedFlowable<K, V>> actual, Function<? super T, ? extends K> keySelector, + Function<? super T, ? extends V> valueSelector, int bufferSize, boolean delayError, + Map<Object, GroupedUnicast<K, V>> groups, Queue<GroupedUnicast<K, V>> evictedGroups) { + this.downstream = actual; this.keySelector = keySelector; this.valueSelector = valueSelector; this.bufferSize = bufferSize; this.delayError = delayError; - this.groups = new ConcurrentHashMap<Object, GroupedUnicast<K, V>>(); + this.groups = groups; + this.evictedGroups = evictedGroups; this.queue = new SpscLinkedArrayQueue<GroupedFlowable<K, V>>(bufferSize); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(bufferSize); } } @@ -110,7 +144,7 @@ public void onNext(T t) { key = keySelector.apply(t); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -138,13 +172,15 @@ public void onNext(T t) { v = ObjectHelper.requireNonNull(valueSelector.apply(t), "The valueSelector returned null"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } group.onNext(v); + completeEvictions(); + if (newGroup) { q.offer(group); drain(); @@ -157,13 +193,16 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } + done = true; for (GroupedUnicast<K, V> g : groups.values()) { g.onError(t); } groups.clear(); - + if (evictedGroups != null) { + evictedGroups.clear(); + } error = t; - done = true; + finished = true; drain(); } @@ -174,7 +213,11 @@ public void onComplete() { g.onComplete(); } groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } done = true; + finished = true; drain(); } } @@ -192,8 +235,23 @@ public void cancel() { // cancelling the main source means we don't want any more groups // but running groups still require new values if (cancelled.compareAndSet(false, true)) { + completeEvictions(); if (groupCount.decrementAndGet() == 0) { - s.cancel(); + upstream.cancel(); + } + } + } + + private void completeEvictions() { + if (evictedGroups != null) { + int count = 0; + GroupedUnicast<K, V> evictedGroup; + while ((evictedGroup = evictedGroups.poll()) != null) { + evictedGroup.onComplete(); + count++; + } + if (count != 0) { + groupCount.addAndGet(-count); } } } @@ -202,9 +260,9 @@ public void cancel(K key) { Object mapKey = key != null ? key : NULL_KEY; groups.remove(mapKey); if (groupCount.decrementAndGet() == 0) { - s.cancel(); + upstream.cancel(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -226,15 +284,14 @@ void drainFused() { int missed = 1; final SpscLinkedArrayQueue<GroupedFlowable<K, V>> q = this.queue; - final Subscriber<? super GroupedFlowable<K, V>> a = this.actual; + final Subscriber<? super GroupedFlowable<K, V>> a = this.downstream; for (;;) { if (cancelled.get()) { - q.clear(); return; } - boolean d = done; + boolean d = finished; if (d && !delayError) { Throwable ex = error; @@ -268,7 +325,7 @@ void drainNormal() { int missed = 1; final SpscLinkedArrayQueue<GroupedFlowable<K, V>> q = this.queue; - final Subscriber<? super GroupedFlowable<K, V>> a = this.actual; + final Subscriber<? super GroupedFlowable<K, V>> a = this.downstream; for (;;) { @@ -276,7 +333,7 @@ void drainNormal() { long e = 0L; while (e != r) { - boolean d = done; + boolean d = finished; GroupedFlowable<K, V> t = q.poll(); @@ -295,7 +352,7 @@ void drainNormal() { e++; } - if (e == r && checkTerminated(done, q.isEmpty(), a, q)) { + if (e == r && checkTerminated(finished, q.isEmpty(), a, q)) { return; } @@ -303,7 +360,7 @@ void drainNormal() { if (r != Long.MAX_VALUE) { requested.addAndGet(-e); } - s.request(e); + upstream.request(e); } missed = addAndGet(-missed); @@ -372,6 +429,20 @@ public boolean isEmpty() { } } + static final class EvictionAction<K, V> implements Consumer<GroupedUnicast<K, V>> { + + final Queue<GroupedUnicast<K, V>> evictedGroups; + + EvictionAction(Queue<GroupedUnicast<K, V>> evictedGroups) { + this.evictedGroups = evictedGroups; + } + + @Override + public void accept(GroupedUnicast<K, V> value) { + evictedGroups.offer(value); + } + } + static final class GroupedUnicast<K, T> extends GroupedFlowable<K, T> { final State<T, K> state; @@ -447,6 +518,7 @@ public void request(long n) { public void cancel() { if (cancelled.compareAndSet(false, true)) { parent.cancel(key); + drain(); } } @@ -497,7 +569,6 @@ void drainFused() { for (;;) { if (a != null) { if (cancelled.get()) { - q.clear(); return; } @@ -552,7 +623,7 @@ void drainNormal() { T v = q.poll(); boolean empty = v == null; - if (checkTerminated(d, empty, a, delayError)) { + if (checkTerminated(d, empty, a, delayError, e)) { return; } @@ -565,7 +636,7 @@ void drainNormal() { e++; } - if (e == r && checkTerminated(done, q.isEmpty(), a, delayError)) { + if (e == r && checkTerminated(done, q.isEmpty(), a, delayError, e)) { return; } @@ -573,7 +644,7 @@ void drainNormal() { if (r != Long.MAX_VALUE) { requested.addAndGet(-e); } - parent.s.request(e); + parent.upstream.request(e); } } @@ -587,9 +658,15 @@ void drainNormal() { } } - boolean checkTerminated(boolean d, boolean empty, Subscriber<? super T> a, boolean delayError) { + boolean checkTerminated(boolean d, boolean empty, Subscriber<? super T> a, boolean delayError, long emitted) { if (cancelled.get()) { - queue.clear(); + // make sure buffered items can get replenished + while (queue.poll() != null) { + emitted++; + } + if (emitted != 0) { + parent.upstream.request(emitted); + } return true; } @@ -638,22 +715,35 @@ public T poll() { produced++; return v; } - int p = produced; - if (p != 0) { - produced = 0; - parent.s.request(p); - } + tryReplenish(); return null; } @Override public boolean isEmpty() { - return queue.isEmpty(); + if (queue.isEmpty()) { + tryReplenish(); + return true; + } + return false; + } + + void tryReplenish() { + int p = produced; + if (p != 0) { + produced = 0; + parent.upstream.request(p); + } } @Override public void clear() { - queue.clear(); + // make sure buffered items can get replenished + SpscLinkedArrayQueue<T> q = queue; + while (q.poll() != null) { + produced++; + } + tryReplenish(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java index 499bc38ff7..7cf3adb7b8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java @@ -89,10 +89,9 @@ interface JoinSupport { static final class GroupJoinSubscription<TLeft, TRight, TLeftEnd, TRightEnd, R> extends AtomicInteger implements Subscription, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final AtomicLong requested; @@ -131,7 +130,7 @@ static final class GroupJoinSubscription<TLeft, TRight, TLeftEnd, TRightEnd, R> GroupJoinSubscription(Subscriber<? super R> actual, Function<? super TLeft, ? extends Publisher<TLeftEnd>> leftEnd, Function<? super TRight, ? extends Publisher<TRightEnd>> rightEnd, BiFunction<? super TLeft, ? super Flowable<TRight>, ? extends R> resultSelector) { - this.actual = actual; + this.downstream = actual; this.requested = new AtomicLong(); this.disposables = new CompositeDisposable(); this.queue = new SpscLinkedArrayQueue<Object>(bufferSize()); @@ -195,7 +194,7 @@ void drain() { int missed = 1; SpscLinkedArrayQueue<Object> q = queue; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; for (;;) { for (;;) { @@ -412,14 +411,12 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -465,14 +462,12 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); + return get() == SubscriptionHelper.CANCELLED; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java index a0d3a33467..0e5f7cc14f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java @@ -37,45 +37,45 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class HideSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; - Subscription s; + Subscription upstream; - HideSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + HideSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java index 270f99e31f..443baf3989 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java @@ -32,19 +32,19 @@ protected void subscribeActual(final Subscriber<? super T> t) { } static final class IgnoreElementsSubscriber<T> implements FlowableSubscriber<T>, QueueSubscription<T> { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; - Subscription s; + Subscription upstream; - IgnoreElementsSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + IgnoreElementsSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -56,12 +56,12 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override @@ -97,7 +97,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java index 88e7577c5a..fc59ae9d8e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java @@ -40,19 +40,19 @@ public Flowable<T> fuseToFlowable() { } static final class IgnoreElementsSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final CompletableObserver actual; + final CompletableObserver downstream; - Subscription s; + Subscription upstream; - IgnoreElementsSubscriber(CompletableObserver actual) { - this.actual = actual; + IgnoreElementsSubscriber(CompletableObserver downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -64,25 +64,25 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; - actual.onComplete(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onComplete(); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java index ca97aa34e6..35a09373cc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java @@ -62,14 +62,14 @@ static final class IntervalSubscriber extends AtomicLong private static final long serialVersionUID = -2809475196591179431L; - final Subscriber<? super Long> actual; + final Subscriber<? super Long> downstream; long count; final AtomicReference<Disposable> resource = new AtomicReference<Disposable>(); - IntervalSubscriber(Subscriber<? super Long> actual) { - this.actual = actual; + IntervalSubscriber(Subscriber<? super Long> downstream) { + this.downstream = downstream; } @Override @@ -90,10 +90,10 @@ public void run() { long r = get(); if (r != 0L) { - actual.onNext(count++); + downstream.onNext(count++); BackpressureHelper.produced(this, 1); } else { - actual.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); + downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); DisposableHelper.dispose(resource); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java index fa697dba41..739460884b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java @@ -66,7 +66,7 @@ static final class IntervalRangeSubscriber extends AtomicLong private static final long serialVersionUID = -2809475196591179431L; - final Subscriber<? super Long> actual; + final Subscriber<? super Long> downstream; final long end; long count; @@ -74,7 +74,7 @@ static final class IntervalRangeSubscriber extends AtomicLong final AtomicReference<Disposable> resource = new AtomicReference<Disposable>(); IntervalRangeSubscriber(Subscriber<? super Long> actual, long start, long end) { - this.actual = actual; + this.downstream = actual; this.count = start; this.end = end; } @@ -98,11 +98,11 @@ public void run() { if (r != 0L) { long c = count; - actual.onNext(c); + downstream.onNext(c); if (c == end) { if (resource.get() != DisposableHelper.DISPOSED) { - actual.onComplete(); + downstream.onComplete(); } DisposableHelper.dispose(resource); return; @@ -114,7 +114,7 @@ public void run() { decrementAndGet(); } } else { - actual.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); + downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); DisposableHelper.dispose(resource); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java index 36674f13fd..2bd33b64a3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java @@ -73,10 +73,9 @@ protected void subscribeActual(Subscriber<? super R> s) { static final class JoinSubscription<TLeft, TRight, TLeftEnd, TRightEnd, R> extends AtomicInteger implements Subscription, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final AtomicLong requested; @@ -115,7 +114,7 @@ static final class JoinSubscription<TLeft, TRight, TLeftEnd, TRightEnd, R> JoinSubscription(Subscriber<? super R> actual, Function<? super TLeft, ? extends Publisher<TLeftEnd>> leftEnd, Function<? super TRight, ? extends Publisher<TRightEnd>> rightEnd, BiFunction<? super TLeft, ? super TRight, ? extends R> resultSelector) { - this.actual = actual; + this.downstream = actual; this.requested = new AtomicLong(); this.disposables = new CompositeDisposable(); this.queue = new SpscLinkedArrayQueue<Object>(bufferSize()); @@ -175,7 +174,7 @@ void drain() { int missed = 1; SpscLinkedArrayQueue<Object> q = queue; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; for (;;) { for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java index ef36a4bd80..e27efeb403 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java @@ -41,33 +41,33 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class LastSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Subscription s; + Subscription upstream; T item; - LastSubscriber(MaybeObserver<? super T> actual) { - this.actual = actual; + LastSubscriber(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -80,20 +80,20 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; item = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; T v = item; if (v != null) { item = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java index 8c8a12ee05..344c6f3668 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java @@ -47,36 +47,36 @@ protected void subscribeActual(SingleObserver<? super T> observer) { static final class LastSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final T defaultItem; - Subscription s; + Subscription upstream; T item; LastSubscriber(SingleObserver<? super T> actual, T defaultItem) { - this.actual = actual; + this.downstream = actual; this.defaultItem = defaultItem; } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -89,25 +89,25 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; item = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; T v = item; if (v != null) { item = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { v = defaultItem; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java new file mode 100644 index 0000000000..58004d0c69 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Limits both the total request amount and items received from the upstream. + * <p>History: 2.1.6 - experimental + * @param <T> the source and output value type + * @since 2.2 + */ +public final class FlowableLimit<T> extends AbstractFlowableWithUpstream<T, T> { + + final long n; + + public FlowableLimit(Flowable<T> source, long n) { + super(source); + this.n = n; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new LimitSubscriber<T>(s, n)); + } + + static final class LimitSubscriber<T> + extends AtomicLong + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = 2288246011222124525L; + + final Subscriber<? super T> downstream; + + long remaining; + + Subscription upstream; + + LimitSubscriber(Subscriber<? super T> actual, long remaining) { + this.downstream = actual; + this.remaining = remaining; + lazySet(remaining); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + if (remaining == 0L) { + s.cancel(); + EmptySubscription.complete(downstream); + } else { + this.upstream = s; + downstream.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + long r = remaining; + if (r > 0L) { + remaining = --r; + downstream.onNext(t); + if (r == 0L) { + upstream.cancel(); + downstream.onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (remaining > 0L) { + remaining = 0L; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (remaining > 0L) { + remaining = 0L; + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + long r = get(); + if (r == 0L) { + break; + } + long toRequest; + if (r <= n) { + toRequest = r; + } else { + toRequest = n; + } + long u = r - toRequest; + if (compareAndSet(r, u)) { + upstream.request(toRequest); + break; + } + } + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java index e9df5a6726..7b6632dcf9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.flowable; import org.reactivestreams.Subscriber; @@ -54,7 +53,7 @@ public void onNext(T t) { } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return; } @@ -66,7 +65,7 @@ public void onNext(T t) { fail(ex); return; } - actual.onNext(v); + downstream.onNext(v); } @Override @@ -97,7 +96,7 @@ public void onNext(T t) { } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return; } @@ -109,7 +108,7 @@ public void onNext(T t) { fail(ex); return; } - actual.onNext(v); + downstream.onNext(v); } @Override @@ -126,7 +125,7 @@ public boolean tryOnNext(T t) { fail(ex); return true; } - return actual.tryOnNext(v); + return downstream.tryOnNext(v); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java index 0f0ec91d68..4b90d60ea3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java @@ -71,12 +71,12 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(onNextMapper.apply(t), "The onNext publisher returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } produced++; - actual.onNext(p); + downstream.onNext(p); } @Override @@ -87,7 +87,7 @@ public void onError(Throwable t) { p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError publisher returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } @@ -102,7 +102,7 @@ public void onComplete() { p = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onComplete publisher returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java index 03b9fe9634..3378af4b15 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.flowable; import org.reactivestreams.*; @@ -22,10 +21,10 @@ /** * Map working with an arbitrary Publisher source. - * + * <p>History: 2.0.7 - experimental * @param <T> the input value type * @param <U> the output value type - * @since 2.0.7 - experimental + * @since 2.1 */ public final class FlowableMapPublisher<T, U> extends Flowable<U> { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java index 3bceba2dea..91f58105ee 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java @@ -34,14 +34,14 @@ static final class MaterializeSubscriber<T> extends SinglePostCompleteSubscriber private static final long serialVersionUID = -3740826063558713822L; - MaterializeSubscriber(Subscriber<? super Notification<T>> actual) { - super(actual); + MaterializeSubscriber(Subscriber<? super Notification<T>> downstream) { + super(downstream); } @Override public void onNext(T t) { produced++; - actual.onNext(Notification.createOnNext(t)); + downstream.onNext(Notification.createOnNext(t)); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java new file mode 100644 index 0000000000..c65386bbb1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +/** + * Merges a Flowable and a Completable by emitting the items of the Flowable and waiting until + * both the Flowable and Completable complete normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Flowable + * @since 2.2 + */ +public final class FlowableMergeWithCompletable<T> extends AbstractFlowableWithUpstream<T, T> { + + final CompletableSource other; + + public FlowableMergeWithCompletable(Flowable<T> source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + MergeWithSubscriber<T> parent = new MergeWithSubscriber<T>(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithSubscriber<T> extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber<? super T> downstream; + + final AtomicReference<Subscription> mainSubscription; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + volatile boolean mainDone; + + volatile boolean otherDone; + + MergeWithSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference<Subscription>(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(mainSubscription, requested, s); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable ex) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, ex, this, error); + } + + @Override + public void onComplete() { + mainDone = true; + if (otherDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(mainSubscription, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + } + + void otherError(Throwable ex) { + SubscriptionHelper.cancel(mainSubscription); + HalfSerializer.onError(downstream, ex, this, error); + } + + void otherComplete() { + otherDone = true; + if (mainDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + static final class OtherObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithSubscriber<?> parent; + + OtherObserver(MergeWithSubscriber<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java new file mode 100644 index 0000000000..1787d5fce3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java @@ -0,0 +1,357 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Observable + * @since 2.2 + */ +public final class FlowableMergeWithMaybe<T> extends AbstractFlowableWithUpstream<T, T> { + + final MaybeSource<? extends T> other; + + public FlowableMergeWithMaybe(Flowable<T> source, MaybeSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + MergeWithObserver<T> parent = new MergeWithObserver<T>(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver<T> extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber<? super T> downstream; + + final AtomicReference<Subscription> mainSubscription; + + final OtherObserver<T> otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + final int prefetch; + + final int limit; + + volatile SimplePlainQueue<T> queue; + + T singleItem; + + volatile boolean cancelled; + + volatile boolean mainDone; + + volatile int otherState; + + long emitted; + + int consumed; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Subscriber<? super T> downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference<Subscription>(); + this.otherObserver = new OtherObserver<T>(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.prefetch = bufferSize(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + SimplePlainQueue<T> q = queue; + if (q == null || q.isEmpty()) { + + emitted = e + 1; + downstream.onNext(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + mainSubscription.get().request(c); + } else { + consumed = c; + } + } else { + q.offer(t); + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + + emitted = e + 1; + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (decrementAndGet() == 0) { + return; + } + } + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + SubscriptionHelper.cancel(mainSubscription); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void otherComplete() { + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + drain(); + } + + SimplePlainQueue<T> getOrCreateQueue() { + SimplePlainQueue<T> q = queue; + if (q == null) { + q = new SpscArrayQueue<T>(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Subscriber<? super T> actual = this.downstream; + int missed = 1; + long e = emitted; + int c = consumed; + int lim = limit; + for (;;) { + + long r = requested.get(); + + while (e != r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + + e++; + continue; + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + + e++; + + if (++c == lim) { + c = 0; + mainSubscription.get().request(lim); + } + } + + if (e == r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + boolean empty = q == null || q.isEmpty(); + + if (d && empty && otherState == 2) { + queue = null; + actual.onComplete(); + return; + } + } + + emitted = e; + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver<T> extends AtomicReference<Disposable> + implements MaybeObserver<T> { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver<T> parent; + + OtherObserver(MergeWithObserver<T> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java new file mode 100644 index 0000000000..486cb73f8c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java @@ -0,0 +1,347 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Observable + * @since 2.2 + */ +public final class FlowableMergeWithSingle<T> extends AbstractFlowableWithUpstream<T, T> { + + final SingleSource<? extends T> other; + + public FlowableMergeWithSingle(Flowable<T> source, SingleSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + MergeWithObserver<T> parent = new MergeWithObserver<T>(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver<T> extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber<? super T> downstream; + + final AtomicReference<Subscription> mainSubscription; + + final OtherObserver<T> otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + final int prefetch; + + final int limit; + + volatile SimplePlainQueue<T> queue; + + T singleItem; + + volatile boolean cancelled; + + volatile boolean mainDone; + + volatile int otherState; + + long emitted; + + int consumed; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Subscriber<? super T> downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference<Subscription>(); + this.otherObserver = new OtherObserver<T>(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.prefetch = bufferSize(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + SimplePlainQueue<T> q = queue; + if (q == null || q.isEmpty()) { + + emitted = e + 1; + downstream.onNext(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + mainSubscription.get().request(c); + } else { + consumed = c; + } + } else { + q.offer(t); + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + + emitted = e + 1; + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (decrementAndGet() == 0) { + return; + } + } + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + SubscriptionHelper.cancel(mainSubscription); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + SimplePlainQueue<T> getOrCreateQueue() { + SimplePlainQueue<T> q = queue; + if (q == null) { + q = new SpscArrayQueue<T>(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Subscriber<? super T> actual = this.downstream; + int missed = 1; + long e = emitted; + int c = consumed; + int lim = limit; + for (;;) { + + long r = requested.get(); + + while (e != r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + + e++; + continue; + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + + e++; + + if (++c == lim) { + c = 0; + mainSubscription.get().request(lim); + } + } + + if (e == r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + boolean empty = q == null || q.isEmpty(); + + if (d && empty && otherState == 2) { + queue = null; + actual.onComplete(); + return; + } + } + + emitted = e; + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver<T> extends AtomicReference<Disposable> + implements SingleObserver<T> { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver<T> parent; + + OtherObserver(MergeWithObserver<T> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java index aaf515bf77..3431f3a50b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java @@ -72,7 +72,7 @@ abstract static class BaseObserveOnSubscriber<T> final AtomicLong requested; - Subscription s; + Subscription upstream; SimpleQueue<T> queue; @@ -109,7 +109,7 @@ public final void onNext(T t) { return; } if (!queue.offer(t)) { - s.cancel(); + upstream.cancel(); error = new MissingBackpressureException("Queue is full?!"); done = true; @@ -151,10 +151,10 @@ public final void cancel() { } cancelled = true; - s.cancel(); + upstream.cancel(); worker.dispose(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -191,6 +191,7 @@ final boolean checkTerminated(boolean d, boolean empty, Subscriber<?> a) { if (d) { if (delayError) { if (empty) { + cancelled = true; Throwable e = error; if (e != null) { a.onError(e); @@ -203,12 +204,14 @@ final boolean checkTerminated(boolean d, boolean empty, Subscriber<?> a) { } else { Throwable e = error; if (e != null) { + cancelled = true; clear(); a.onError(e); worker.dispose(); return true; } else if (empty) { + cancelled = true; a.onComplete(); worker.dispose(); return true; @@ -244,7 +247,7 @@ static final class ObserveOnSubscriber<T> extends BaseObserveOnSubscriber<T> private static final long serialVersionUID = -4547113800637756442L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; ObserveOnSubscriber( Subscriber<? super T> actual, @@ -252,13 +255,13 @@ static final class ObserveOnSubscriber<T> extends BaseObserveOnSubscriber<T> boolean delayError, int prefetch) { super(worker, delayError, prefetch); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") @@ -271,14 +274,14 @@ public void onSubscribe(Subscription s) { queue = f; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } else if (m == ASYNC) { sourceMode = ASYNC; queue = f; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); @@ -288,7 +291,7 @@ public void onSubscribe(Subscription s) { queue = new SpscArrayQueue<T>(prefetch); - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); } @@ -298,7 +301,7 @@ public void onSubscribe(Subscription s) { void runSync() { int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final SimpleQueue<T> q = queue; long e = produced; @@ -314,7 +317,8 @@ void runSync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + cancelled = true; + upstream.cancel(); a.onError(ex); worker.dispose(); return; @@ -324,6 +328,7 @@ void runSync() { return; } if (v == null) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -339,6 +344,7 @@ void runSync() { } if (q.isEmpty()) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -361,7 +367,7 @@ void runSync() { void runAsync() { int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final SimpleQueue<T> q = queue; long e = produced; @@ -379,7 +385,8 @@ void runAsync() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + cancelled = true; + upstream.cancel(); q.clear(); a.onError(ex); @@ -404,7 +411,7 @@ void runAsync() { if (r != Long.MAX_VALUE) { r = requested.addAndGet(-e); } - s.request(e); + upstream.request(e); e = 0L; } } @@ -438,14 +445,15 @@ void runBackfused() { boolean d = done; - actual.onNext(null); + downstream.onNext(null); if (d) { + cancelled = true; Throwable e = error; if (e != null) { - actual.onError(e); + downstream.onError(e); } else { - actual.onComplete(); + downstream.onComplete(); } worker.dispose(); return; @@ -466,7 +474,7 @@ public T poll() throws Exception { long p = produced + 1; if (p == limit) { produced = 0; - s.request(p); + upstream.request(p); } else { produced = p; } @@ -481,7 +489,7 @@ static final class ObserveOnConditionalSubscriber<T> private static final long serialVersionUID = 644624475404284533L; - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; long consumed; @@ -491,13 +499,13 @@ static final class ObserveOnConditionalSubscriber<T> boolean delayError, int prefetch) { super(worker, delayError, prefetch); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") @@ -510,14 +518,14 @@ public void onSubscribe(Subscription s) { queue = f; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } else if (m == ASYNC) { sourceMode = ASYNC; queue = f; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); @@ -527,7 +535,7 @@ public void onSubscribe(Subscription s) { queue = new SpscArrayQueue<T>(prefetch); - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); } @@ -537,7 +545,7 @@ public void onSubscribe(Subscription s) { void runSync() { int missed = 1; - final ConditionalSubscriber<? super T> a = actual; + final ConditionalSubscriber<? super T> a = downstream; final SimpleQueue<T> q = queue; long e = produced; @@ -552,7 +560,8 @@ void runSync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + cancelled = true; + upstream.cancel(); a.onError(ex); worker.dispose(); return; @@ -562,6 +571,7 @@ void runSync() { return; } if (v == null) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -577,6 +587,7 @@ void runSync() { } if (q.isEmpty()) { + cancelled = true; a.onComplete(); worker.dispose(); return; @@ -599,7 +610,7 @@ void runSync() { void runAsync() { int missed = 1; - final ConditionalSubscriber<? super T> a = actual; + final ConditionalSubscriber<? super T> a = downstream; final SimpleQueue<T> q = queue; long emitted = produced; @@ -617,7 +628,8 @@ void runAsync() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + cancelled = true; + upstream.cancel(); q.clear(); a.onError(ex); @@ -641,7 +653,7 @@ void runAsync() { polled++; if (polled == limit) { - s.request(polled); + upstream.request(polled); polled = 0L; } } @@ -677,14 +689,15 @@ void runBackfused() { boolean d = done; - actual.onNext(null); + downstream.onNext(null); if (d) { + cancelled = true; Throwable e = error; if (e != null) { - actual.onError(e); + downstream.onError(e); } else { - actual.onComplete(); + downstream.onComplete(); } worker.dispose(); return; @@ -705,7 +718,7 @@ public T poll() throws Exception { long p = consumed + 1; if (p == limit) { consumed = 0; - s.request(p); + upstream.request(p); } else { consumed = p; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java index 341cda93c8..8cbb73ebf2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java @@ -50,12 +50,12 @@ static final class BackpressureBufferSubscriber<T> extends BasicIntQueueSubscrip private static final long serialVersionUID = -2514538129242366402L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SimplePlainQueue<T> queue; final boolean delayError; final Action onOverflow; - Subscription s; + Subscription upstream; volatile boolean cancelled; @@ -68,7 +68,7 @@ static final class BackpressureBufferSubscriber<T> extends BasicIntQueueSubscrip BackpressureBufferSubscriber(Subscriber<? super T> actual, int bufferSize, boolean unbounded, boolean delayError, Action onOverflow) { - this.actual = actual; + this.downstream = actual; this.onOverflow = onOverflow; this.delayError = delayError; @@ -85,9 +85,9 @@ static final class BackpressureBufferSubscriber<T> extends BasicIntQueueSubscrip @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -95,7 +95,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { if (!queue.offer(t)) { - s.cancel(); + upstream.cancel(); MissingBackpressureException ex = new MissingBackpressureException("Buffer is full"); try { onOverflow.run(); @@ -107,7 +107,7 @@ public void onNext(T t) { return; } if (outputFused) { - actual.onNext(null); + downstream.onNext(null); } else { drain(); } @@ -118,7 +118,7 @@ public void onError(Throwable t) { error = t; done = true; if (outputFused) { - actual.onError(t); + downstream.onError(t); } else { drain(); } @@ -128,7 +128,7 @@ public void onError(Throwable t) { public void onComplete() { done = true; if (outputFused) { - actual.onComplete(); + downstream.onComplete(); } else { drain(); } @@ -148,9 +148,9 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -160,7 +160,7 @@ void drain() { if (getAndIncrement() == 0) { int missed = 1; final SimplePlainQueue<T> q = queue; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; for (;;) { if (checkTerminated(done, q.isEmpty(), a)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java index 7f3cdf8017..f11a520e7b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java @@ -57,7 +57,7 @@ static final class OnBackpressureBufferStrategySubscriber<T> private static final long serialVersionUID = 3240706908776709697L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Action onOverflow; @@ -69,7 +69,7 @@ static final class OnBackpressureBufferStrategySubscriber<T> final Deque<T> deque; - Subscription s; + Subscription upstream; volatile boolean cancelled; @@ -78,7 +78,7 @@ static final class OnBackpressureBufferStrategySubscriber<T> OnBackpressureBufferStrategySubscriber(Subscriber<? super T> actual, Action onOverflow, BackpressureOverflowStrategy strategy, long bufferSize) { - this.actual = actual; + this.downstream = actual; this.onOverflow = onOverflow; this.strategy = strategy; this.bufferSize = bufferSize; @@ -88,10 +88,10 @@ static final class OnBackpressureBufferStrategySubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -134,12 +134,12 @@ public void onNext(T t) { onOverflow.run(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); } } } else if (callError) { - s.cancel(); + upstream.cancel(); onError(new MissingBackpressureException()); } else { drain(); @@ -174,7 +174,7 @@ public void request(long n) { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { clear(deque); @@ -194,7 +194,7 @@ void drain() { int missed = 1; Deque<T> dq = deque; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; for (;;) { long r = requested.get(); long e = 0L; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java index eb633af079..df5f4a7e86 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java @@ -53,23 +53,23 @@ static final class BackpressureDropSubscriber<T> private static final long serialVersionUID = -6246093802440953054L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Consumer<? super T> onDrop; - Subscription s; + Subscription upstream; boolean done; BackpressureDropSubscriber(Subscriber<? super T> actual, Consumer<? super T> onDrop) { - this.actual = actual; + this.downstream = actual; this.onDrop = onDrop; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -81,7 +81,7 @@ public void onNext(T t) { } long r = get(); if (r != 0L) { - actual.onNext(t); + downstream.onNext(t); BackpressureHelper.produced(this, 1); } else { try { @@ -101,7 +101,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -110,7 +110,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override @@ -122,7 +122,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java index a40f92aefd..556e54b8c3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java @@ -25,12 +25,10 @@ public final class FlowableOnBackpressureError<T> extends AbstractFlowableWithUpstream<T, T> { - public FlowableOnBackpressureError(Flowable<T> source) { super(source); } - @Override protected void subscribeActual(Subscriber<? super T> s) { this.source.subscribe(new BackpressureErrorSubscriber<T>(s)); @@ -40,19 +38,19 @@ static final class BackpressureErrorSubscriber<T> extends AtomicLong implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -3176480756392482682L; - final Subscriber<? super T> actual; - Subscription s; + final Subscriber<? super T> downstream; + Subscription upstream; boolean done; - BackpressureErrorSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + BackpressureErrorSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -64,9 +62,10 @@ public void onNext(T t) { } long r = get(); if (r != 0L) { - actual.onNext(t); + downstream.onNext(t); BackpressureHelper.produced(this, 1); } else { + upstream.cancel(); onError(new MissingBackpressureException("could not emit value due to lack of requests")); } } @@ -78,7 +77,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -87,7 +86,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override @@ -99,7 +98,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java index 2cd36a6ef0..f981b738e6 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java @@ -36,9 +36,9 @@ static final class BackpressureLatestSubscriber<T> extends AtomicInteger impleme private static final long serialVersionUID = 163080509307634843L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; - Subscription s; + Subscription upstream; volatile boolean done; Throwable error; @@ -49,15 +49,15 @@ static final class BackpressureLatestSubscriber<T> extends AtomicInteger impleme final AtomicReference<T> current = new AtomicReference<T>(); - BackpressureLatestSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + BackpressureLatestSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -93,7 +93,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { current.lazySet(null); @@ -105,7 +105,7 @@ void drain() { if (getAndIncrement() != 0) { return; } - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; int missed = 1; final AtomicLong r = requested; final AtomicReference<T> q = current; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java index dd1198d0a9..7110d086e0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java @@ -18,6 +18,7 @@ import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionArbiter; import io.reactivex.plugins.RxJavaPlugins; @@ -35,30 +36,37 @@ public FlowableOnErrorNext(Flowable<T> source, @Override protected void subscribeActual(Subscriber<? super T> s) { OnErrorNextSubscriber<T> parent = new OnErrorNextSubscriber<T>(s, nextSupplier, allowFatal); - s.onSubscribe(parent.arbiter); + s.onSubscribe(parent); source.subscribe(parent); } - static final class OnErrorNextSubscriber<T> implements FlowableSubscriber<T> { - final Subscriber<? super T> actual; + static final class OnErrorNextSubscriber<T> + extends SubscriptionArbiter + implements FlowableSubscriber<T> { + private static final long serialVersionUID = 4063763155303814625L; + + final Subscriber<? super T> downstream; + final Function<? super Throwable, ? extends Publisher<? extends T>> nextSupplier; + final boolean allowFatal; - final SubscriptionArbiter arbiter; boolean once; boolean done; + long produced; + OnErrorNextSubscriber(Subscriber<? super T> actual, Function<? super Throwable, ? extends Publisher<? extends T>> nextSupplier, boolean allowFatal) { - this.actual = actual; + super(false); + this.downstream = actual; this.nextSupplier = nextSupplier; this.allowFatal = allowFatal; - this.arbiter = new SubscriptionArbiter(); } @Override public void onSubscribe(Subscription s) { - arbiter.setSubscription(s); + setSubscription(s); } @Override @@ -66,10 +74,10 @@ public void onNext(T t) { if (done) { return; } - actual.onNext(t); if (!once) { - arbiter.produced(1L); + produced++; } + downstream.onNext(t); } @Override @@ -79,31 +87,29 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } - actual.onError(t); + downstream.onError(t); return; } once = true; if (allowFatal && !(t instanceof Exception)) { - actual.onError(t); + downstream.onError(t); return; } Publisher<? extends T> p; try { - p = nextSupplier.apply(t); + p = ObjectHelper.requireNonNull(nextSupplier.apply(t), "The nextSupplier returned a null Publisher"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } - if (p == null) { - NullPointerException npe = new NullPointerException("Publisher is null"); - npe.initCause(t); - actual.onError(npe); - return; + long mainProduced = produced; + if (mainProduced != 0L) { + produced(mainProduced); } p.subscribe(this); @@ -116,7 +122,7 @@ public void onComplete() { } done = true; once = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java index 333aa1cb27..baf47d608a 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java @@ -47,7 +47,7 @@ static final class OnErrorReturnSubscriber<T> @Override public void onNext(T t) { produced++; - actual.onNext(t); + downstream.onNext(t); } @Override @@ -57,7 +57,7 @@ public void onError(Throwable t) { v = ObjectHelper.requireNonNull(valueSupplier.apply(t), "The valueSupplier returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(t, ex)); + downstream.onError(new CompositeException(t, ex)); return; } complete(v); @@ -65,7 +65,7 @@ public void onError(Throwable t) { @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java index 147f9ecc11..b325d2b78d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java @@ -33,7 +33,8 @@ * manner. * @param <T> the value type */ -public final class FlowablePublish<T> extends ConnectableFlowable<T> implements HasUpstreamPublisher<T> { +public final class FlowablePublish<T> extends ConnectableFlowable<T> +implements HasUpstreamPublisher<T>, FlowablePublishClassic<T> { /** * Indicates this child has been cancelled: the state is swapped in atomically and * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. @@ -77,6 +78,20 @@ public Publisher<T> source() { return source; } + /** + * The internal buffer size of this FloawblePublish operator. + * @return The internal buffer size of this FloawblePublish operator. + */ + @Override + public int publishBufferSize() { + return bufferSize; + } + + @Override + public Publisher<T> publishSource() { + return source; + } + @Override protected void subscribeActual(Subscriber<? super T> s) { onSubscribe.subscribe(s); @@ -148,14 +163,14 @@ static final class PublishSubscriber<T> final int bufferSize; /** Tracks the subscribed InnerSubscribers. */ - final AtomicReference<InnerSubscriber[]> subscribers; + final AtomicReference<InnerSubscriber<T>[]> subscribers; /** * Atomically changed from false to true by connect to make sure the * connection is only performed by one thread. */ final AtomicBoolean shouldConnect; - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + final AtomicReference<Subscription> upstream = new AtomicReference<Subscription>(); /** Contains either an onComplete or an onError token from upstream. */ volatile Object terminalEvent; @@ -165,8 +180,9 @@ static final class PublishSubscriber<T> /** Holds notifications from upstream. */ volatile SimpleQueue<T> queue; + @SuppressWarnings("unchecked") PublishSubscriber(AtomicReference<PublishSubscriber<T>> current, int bufferSize) { - this.subscribers = new AtomicReference<InnerSubscriber[]>(EMPTY); + this.subscribers = new AtomicReference<InnerSubscriber<T>[]>(EMPTY); this.current = current; this.shouldConnect = new AtomicBoolean(); this.bufferSize = bufferSize; @@ -175,10 +191,11 @@ static final class PublishSubscriber<T> @Override public void dispose() { if (subscribers.get() != TERMINATED) { + @SuppressWarnings("unchecked") InnerSubscriber[] ps = subscribers.getAndSet(TERMINATED); if (ps != TERMINATED) { current.compareAndSet(PublishSubscriber.this, null); - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); } } } @@ -190,12 +207,12 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription<T> qs = (QueueSubscription<T>) s; - int m = qs.requestFusion(QueueSubscription.ANY); + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { sourceMode = m; queue = qs; @@ -228,6 +245,7 @@ public void onNext(T t) { // loop to act on the current state serially dispatch(); } + @Override public void onError(Throwable e) { // The observer front is accessed serially as required by spec so @@ -241,6 +259,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @Override public void onComplete() { // The observer front is accessed serially as required by spec so @@ -263,7 +282,7 @@ boolean add(InnerSubscriber<T> producer) { // the state can change so we do a CAS loop to achieve atomicity for (;;) { // get the current producer array - InnerSubscriber[] c = subscribers.get(); + InnerSubscriber<T>[] c = subscribers.get(); // if this subscriber-to-source reached a terminal state by receiving // an onError or onComplete, just refuse to add the new producer if (c == TERMINATED) { @@ -271,7 +290,8 @@ boolean add(InnerSubscriber<T> producer) { } // we perform a copy-on-write logic int len = c.length; - InnerSubscriber[] u = new InnerSubscriber[len + 1]; + @SuppressWarnings("unchecked") + InnerSubscriber<T>[] u = new InnerSubscriber[len + 1]; System.arraycopy(c, 0, u, 0, len); u[len] = producer; // try setting the subscribers array @@ -287,11 +307,12 @@ boolean add(InnerSubscriber<T> producer) { * Atomically removes the given InnerSubscriber from the subscribers array. * @param producer the producer to remove */ + @SuppressWarnings("unchecked") void remove(InnerSubscriber<T> producer) { // the state can change so we do a CAS loop to achieve atomicity for (;;) { // let's read the current subscribers array - InnerSubscriber[] c = subscribers.get(); + InnerSubscriber<T>[] c = subscribers.get(); int len = c.length; // if it is either empty or terminated, there is nothing to remove so we quit if (len == 0) { @@ -311,7 +332,7 @@ void remove(InnerSubscriber<T> producer) { return; } // we do copy-on-write logic here - InnerSubscriber[] u; + InnerSubscriber<T>[] u; // we don't create a new empty array if producer was the single inhabitant // but rather reuse an empty array if (len == 1) { @@ -340,6 +361,7 @@ void remove(InnerSubscriber<T> producer) { * @param empty set to true if the queue is empty * @return true if there is indeed a terminal condition */ + @SuppressWarnings("unchecked") boolean checkTerminated(Object term, boolean empty) { // first of all, check if there is actually a terminal event if (term != null) { @@ -404,6 +426,17 @@ void dispatch() { return; } int missed = 1; + + // saving a local copy because this will be accessed after every item + // delivered to detect changes in the subscribers due to an onNext + // and thus not dropping items + AtomicReference<InnerSubscriber<T>[]> subscribers = this.subscribers; + + // We take a snapshot of the current child subscribers. + // Concurrent subscribers may miss this iteration, but it is to be expected + InnerSubscriber<T>[] ps = subscribers.get(); + + outer: for (;;) { /* * We need to read terminalEvent before checking the queue for emptiness because @@ -434,10 +467,6 @@ void dispatch() { // this loop is the only one which can turn a non-empty queue into an empty one // and as such, no need to ask the queue itself again for that. if (!empty) { - // We take a snapshot of the current child subscribers. - // Concurrent subscribers may miss this iteration, but it is to be expected - @SuppressWarnings("unchecked") - InnerSubscriber<T>[] ps = subscribers.get(); int len = ps.length; // Let's assume everyone requested the maximum value. @@ -452,14 +481,11 @@ void dispatch() { long r = ip.get(); // if there is one child subscriber that hasn't requested yet // we can't emit anything to anyone - if (r >= 0L) { - maxRequested = Math.min(maxRequested, r); - } else - // cancellation is indicated by a special value - if (r == CANCELLED) { + if (r != CANCELLED) { + maxRequested = Math.min(maxRequested, r - ip.emitted); + } else { cancelled++; } - // we ignore those with NOT_REQUESTED as if they aren't even there } // it may happen everyone has cancelled between here and subscribers.get() @@ -473,7 +499,7 @@ void dispatch() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.get().cancel(); + upstream.get().cancel(); term = NotificationLite.error(ex); terminalEvent = term; v = null; @@ -484,7 +510,7 @@ void dispatch() { } // otherwise, just ask for a new value if (sourceMode != QueueSubscription.SYNC) { - s.get().request(1); + upstream.get().request(1); } // and retry emitting to potential new child subscribers continue; @@ -501,7 +527,7 @@ void dispatch() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.get().cancel(); + upstream.get().cancel(); term = NotificationLite.error(ex); terminalEvent = term; v = null; @@ -518,26 +544,50 @@ void dispatch() { } // we need to unwrap potential nulls T value = NotificationLite.getValue(v); + + boolean subscribersChanged = false; + // let's emit this value to all child subscribers for (InnerSubscriber<T> ip : ps) { // if ip.get() is negative, the child has either cancelled in the // meantime or hasn't requested anything yet // this eager behavior will skip cancelled children in case // multiple values are available in the queue - if (ip.get() > 0L) { + long ipr = ip.get(); + if (ipr != CANCELLED) { + if (ipr != Long.MAX_VALUE) { + // indicate this child has received 1 element + ip.emitted++; + } ip.child.onNext(value); - // indicate this child has received 1 element - ip.produced(1); + } else { + subscribersChanged = true; } } // indicate we emitted one element d++; + + // see if the array of subscribers changed as a consequence + // of emission or concurrent activity + InnerSubscriber<T>[] freshArray = subscribers.get(); + if (subscribersChanged || freshArray != ps) { + ps = freshArray; + + // if we did emit at least one element, request more to replenish the queue + if (d != 0) { + if (sourceMode != QueueSubscription.SYNC) { + upstream.get().request(d); + } + } + + continue outer; + } } // if we did emit at least one element, request more to replenish the queue - if (d > 0) { + if (d != 0) { if (sourceMode != QueueSubscription.SYNC) { - s.get().request(d); + upstream.get().request(d); } } // if we have requests but not an empty queue after emission @@ -552,6 +602,9 @@ void dispatch() { if (missed == 0) { break; } + + // get a fresh copy of the current subscribers + ps = subscribers.get(); } } } @@ -571,6 +624,9 @@ static final class InnerSubscriber<T> extends AtomicLong implements Subscription */ volatile PublishSubscriber<T> parent; + /** Track the number of emitted items (avoids decrementing the request counter). */ + long emitted; + InnerSubscriber(Subscriber<? super T> child) { this.child = child; } @@ -586,15 +642,6 @@ public void request(long n) { } } - /** - * Indicate that values have been emitted to this child subscriber by the dispatch() method. - * @param n the number of items emitted - * @return the updated request value (may indicate how much can be produced or a terminal state) - */ - public long produced(long n) { - return BackpressureHelper.producedCancel(this, n); - } - @Override public void cancel() { long r = get(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java new file mode 100644 index 0000000000..932e0bf003 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java @@ -0,0 +1,484 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.ResettableConnectable; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Shares a single underlying connection to the upstream Publisher + * and multicasts events to all subscribed subscribers until the upstream + * completes or the connection is disposed. + * <p> + * The difference to FlowablePublish is that when the upstream terminates, + * late subscriberss will receive that terminal event until the connection is + * disposed and the ConnectableFlowable is reset to its fresh state. + * + * @param <T> the element type + * @since 2.2.10 + */ +public final class FlowablePublishAlt<T> extends ConnectableFlowable<T> +implements HasUpstreamPublisher<T>, ResettableConnectable { + + final Publisher<T> source; + + final int bufferSize; + + final AtomicReference<PublishConnection<T>> current; + + public FlowablePublishAlt(Publisher<T> source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + this.current = new AtomicReference<PublishConnection<T>>(); + } + + @Override + public Publisher<T> source() { + return source; + } + + /** + * The internal buffer size of this FloawblePublishAlt operator. + * @return The internal buffer size of this FloawblePublishAlt operator. + */ + public int publishBufferSize() { + return bufferSize; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + PublishConnection<T> conn; + boolean doConnect = false; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection<T> fresh = new PublishConnection<T>(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + PublishConnection<T> conn; + + for (;;) { + conn = current.get(); + + // don't create a fresh connection if the current is disposed + if (conn == null) { + PublishConnection<T> fresh = new PublishConnection<T>(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + break; + } + + InnerSubscription<T> inner = new InnerSubscription<T>(s, conn); + s.onSubscribe(inner); + + if (conn.add(inner)) { + if (inner.isCancelled()) { + conn.remove(inner); + } else { + conn.drain(); + } + return; + } + + Throwable ex = conn.error; + if (ex != null) { + s.onError(ex); + } else { + s.onComplete(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection<T>)connection, null); + } + + static final class PublishConnection<T> + extends AtomicInteger + implements FlowableSubscriber<T>, Disposable { + + private static final long serialVersionUID = -1672047311619175801L; + + final AtomicReference<PublishConnection<T>> current; + + final AtomicReference<Subscription> upstream; + + final AtomicBoolean connect; + + final AtomicReference<InnerSubscription<T>[]> subscribers; + + final int bufferSize; + + volatile SimpleQueue<T> queue; + + int sourceMode; + + volatile boolean done; + Throwable error; + + int consumed; + + @SuppressWarnings("rawtypes") + static final InnerSubscription[] EMPTY = new InnerSubscription[0]; + @SuppressWarnings("rawtypes") + static final InnerSubscription[] TERMINATED = new InnerSubscription[0]; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference<PublishConnection<T>> current, int bufferSize) { + this.current = current; + this.upstream = new AtomicReference<Subscription>(); + this.connect = new AtomicBoolean(); + this.bufferSize = bufferSize; + this.subscribers = new AtomicReference<InnerSubscription<T>[]>(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + subscribers.getAndSet(TERMINATED); + current.compareAndSet(this, null); + SubscriptionHelper.cancel(upstream); + } + + @Override + public boolean isDisposed() { + return subscribers.get() == TERMINATED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription<T> qs = (QueueSubscription<T>) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue<T>(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + // we expect upstream to honor backpressure requests + if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Prefetch queue is full?!")); + return; + } + // since many things can happen concurrently, we have a common dispatch + // loop to act on the current state serially + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + error = t; + done = true; + drain(); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SimpleQueue<T> queue = this.queue; + int consumed = this.consumed; + int limit = this.bufferSize - (this.bufferSize >> 2); + boolean async = this.sourceMode != QueueSubscription.SYNC; + + outer: + for (;;) { + if (queue != null) { + long minDemand = Long.MAX_VALUE; + boolean hasDemand = false; + + InnerSubscription<T>[] innerSubscriptions = subscribers.get(); + + for (InnerSubscription<T> inner : innerSubscriptions) { + long request = inner.get(); + if (request != Long.MIN_VALUE) { + hasDemand = true; + minDemand = Math.min(request - inner.emitted, minDemand); + } + } + + if (!hasDemand) { + minDemand = 0L; + } + + while (minDemand != 0L) { + boolean d = done; + T v; + + try { + v = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + queue.clear(); + done = true; + signalError(ex); + return; + } + + boolean empty = v == null; + + if (checkTerminated(d, empty)) { + return; + } + + if (empty) { + break; + } + + for (InnerSubscription<T> inner : innerSubscriptions) { + if (!inner.isCancelled()) { + inner.downstream.onNext(v); + inner.emitted++; + } + } + + if (async && ++consumed == limit) { + consumed = 0; + upstream.get().request(limit); + } + minDemand--; + + if (innerSubscriptions != subscribers.get()) { + continue outer; + } + } + + if (checkTerminated(done, queue.isEmpty())) { + return; + } + } + + this.consumed = consumed; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (queue == null) { + queue = this.queue; + } + } + } + + @SuppressWarnings("unchecked") + boolean checkTerminated(boolean isDone, boolean isEmpty) { + if (isDone && isEmpty) { + Throwable ex = error; + + if (ex != null) { + signalError(ex); + } else { + for (InnerSubscription<T> inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onComplete(); + } + } + } + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + void signalError(Throwable ex) { + for (InnerSubscription<T> inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onError(ex); + } + } + } + + boolean add(InnerSubscription<T> inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerSubscription<T>[] c = subscribers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + @SuppressWarnings("unchecked") + InnerSubscription<T>[] u = new InnerSubscription[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = inner; + // try setting the subscribers array + if (subscribers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + @SuppressWarnings("unchecked") + void remove(InnerSubscription<T> inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current subscribers array + InnerSubscription<T>[] c = subscribers.get(); + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + break; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child subscribers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i] == inner) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerSubscription<T>[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerSubscription[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (subscribers.compareAndSet(c, u)) { + break; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + } + + static final class InnerSubscription<T> extends AtomicLong + implements Subscription { + + private static final long serialVersionUID = 2845000326761540265L; + + final Subscriber<? super T> downstream; + + final PublishConnection<T> parent; + + long emitted; + + InnerSubscription(Subscriber<? super T> downstream, PublishConnection<T> parent) { + this.downstream = downstream; + this.parent = parent; + } + + @Override + public void request(long n) { + BackpressureHelper.addCancel(this, n); + parent.drain(); + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + parent.drain(); + } + } + + public boolean isCancelled() { + return get() == Long.MIN_VALUE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java new file mode 100644 index 0000000000..27ded26592 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import org.reactivestreams.Publisher; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + * <p> + * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param <T> the element type of the sequence + * @since 2.2.10 + */ +public interface FlowablePublishClassic<T> { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + Publisher<T> publishSource(); + + /** + * The internal buffer size of this publish operator. + * @return the internal buffer size of this publish operator + */ + int publishBufferSize(); +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java index ce12c45e9c..46a2dbd7e3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java @@ -75,51 +75,51 @@ protected void subscribeActual(Subscriber<? super R> s) { } static final class OutputCanceller<R> implements FlowableSubscriber<R>, Subscription { - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final MulticastProcessor<?> processor; - Subscription s; + Subscription upstream; OutputCanceller(Subscriber<? super R> actual, MulticastProcessor<?> processor) { - this.actual = actual; + this.downstream = actual; this.processor = processor; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(R t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); processor.dispose(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); processor.dispose(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); processor.dispose(); } } @@ -142,7 +142,7 @@ static final class MulticastProcessor<T> extends Flowable<T> implements Flowable final boolean delayError; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; volatile SimpleQueue<T> queue; @@ -159,13 +159,13 @@ static final class MulticastProcessor<T> extends Flowable<T> implements Flowable this.limit = prefetch - (prefetch >> 2); // request after 75% consumption this.delayError = delayError; this.wip = new AtomicInteger(); - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.subscribers = new AtomicReference<MulticastSubscription<T>[]>(EMPTY); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription<T> qs = (QueueSubscription<T>) s; @@ -194,7 +194,7 @@ public void onSubscribe(Subscription s) { @Override public void dispose() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); if (wip.getAndIncrement() == 0) { SimpleQueue<T> q = queue; if (q != null) { @@ -205,7 +205,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(s.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override @@ -214,7 +214,7 @@ public void onNext(T t) { return; } if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { - s.get().cancel(); + upstream.get().cancel(); onError(new MissingBackpressureException()); return; } @@ -261,10 +261,10 @@ boolean add(MulticastSubscription<T> s) { void remove(MulticastSubscription<T> s) { for (;;) { MulticastSubscription<T>[] current = subscribers.get(); - if (current == TERMINATED || current == EMPTY) { + int n = current.length; + if (n == 0) { return; } - int n = current.length; int j = -1; for (int i = 0; i < n; i++) { @@ -323,9 +323,12 @@ void drain() { int upstreamConsumed = consumed; int localLimit = limit; boolean canRequest = sourceMode != QueueSubscription.SYNC; + AtomicReference<MulticastSubscription<T>[]> subs = subscribers; + MulticastSubscription<T>[] array = subs.get(); + + outer: for (;;) { - MulticastSubscription<T>[] array = subscribers.get(); int n = array.length; @@ -333,16 +336,21 @@ void drain() { long r = Long.MAX_VALUE; for (MulticastSubscription<T> ms : array) { - long u = ms.get(); + long u = ms.get() - ms.emitted; if (u != Long.MIN_VALUE) { if (r > u) { r = u; } + } else { + n--; } } - long e = 0L; - while (e != r) { + if (n == 0) { + r = 0; + } + + while (r != 0) { if (isDisposed()) { q.clear(); return; @@ -364,7 +372,7 @@ void drain() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); errorAll(ex); return; } @@ -385,21 +393,35 @@ void drain() { break; } + boolean subscribersChange = false; + for (MulticastSubscription<T> ms : array) { - if (ms.get() != Long.MIN_VALUE) { - ms.actual.onNext(v); + long msr = ms.get(); + if (msr != Long.MIN_VALUE) { + if (msr != Long.MAX_VALUE) { + ms.emitted++; + } + ms.downstream.onNext(v); + } else { + subscribersChange = true; } } - e++; + r--; if (canRequest && ++upstreamConsumed == localLimit) { upstreamConsumed = 0; - s.get().request(localLimit); + upstream.get().request(localLimit); + } + + MulticastSubscription<T>[] freshArray = subs.get(); + if (subscribersChange || freshArray != array) { + array = freshArray; + continue outer; } } - if (e == r) { + if (r == 0) { if (isDisposed()) { q.clear(); return; @@ -425,10 +447,6 @@ void drain() { return; } } - - for (MulticastSubscription<T> ms : array) { - BackpressureHelper.produced(ms, e); - } } consumed = upstreamConsumed; @@ -439,6 +457,7 @@ void drain() { if (q == null) { q = queue; } + array = subs.get(); } } @@ -446,7 +465,7 @@ void drain() { void errorAll(Throwable ex) { for (MulticastSubscription<T> ms : subscribers.getAndSet(TERMINATED)) { if (ms.get() != Long.MIN_VALUE) { - ms.actual.onError(ex); + ms.downstream.onError(ex); } } } @@ -455,7 +474,7 @@ void errorAll(Throwable ex) { void completeAll() { for (MulticastSubscription<T> ms : subscribers.getAndSet(TERMINATED)) { if (ms.get() != Long.MIN_VALUE) { - ms.actual.onComplete(); + ms.downstream.onComplete(); } } } @@ -465,15 +484,16 @@ static final class MulticastSubscription<T> extends AtomicLong implements Subscription { - private static final long serialVersionUID = 8664815189257569791L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final MulticastProcessor<T> parent; + long emitted; + MulticastSubscription(Subscriber<? super T> actual, MulticastProcessor<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java index 71e50073b8..d4b6b50a59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java @@ -31,6 +31,7 @@ public FlowableRange(int start, int count) { this.start = start; this.end = start + count; } + @Override public void subscribeActual(Subscriber<? super Integer> s) { if (s instanceof ConditionalSubscriber) { @@ -94,13 +95,11 @@ public final void request(long n) { } } - @Override public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -108,20 +107,19 @@ public final void cancel() { static final class RangeSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; - final Subscriber<? super Integer> actual; + final Subscriber<? super Integer> downstream; RangeSubscription(Subscriber<? super Integer> actual, int index, int end) { super(index, end); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { int f = end; - Subscriber<? super Integer> a = actual; + Subscriber<? super Integer> a = downstream; for (int i = index; i != f; i++) { if (cancelled) { @@ -140,7 +138,7 @@ void slowPath(long r) { long e = 0; int f = end; int i = index; - Subscriber<? super Integer> a = actual; + Subscriber<? super Integer> a = downstream; for (;;) { @@ -177,20 +175,19 @@ void slowPath(long r) { static final class RangeConditionalSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; - final ConditionalSubscriber<? super Integer> actual; + final ConditionalSubscriber<? super Integer> downstream; RangeConditionalSubscription(ConditionalSubscriber<? super Integer> actual, int index, int end) { super(index, end); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { int f = end; - ConditionalSubscriber<? super Integer> a = actual; + ConditionalSubscriber<? super Integer> a = downstream; for (int i = index; i != f; i++) { if (cancelled) { @@ -209,7 +206,7 @@ void slowPath(long r) { long e = 0; int f = end; int i = index; - ConditionalSubscriber<? super Integer> a = actual; + ConditionalSubscriber<? super Integer> a = downstream; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java index e049feb815..d641e6a285 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java @@ -102,7 +102,6 @@ public final void cancel() { cancelled = true; } - abstract void fastPath(); abstract void slowPath(long r); @@ -112,17 +111,17 @@ static final class RangeSubscription extends BaseRangeSubscription { private static final long serialVersionUID = 2587302975077663557L; - final Subscriber<? super Long> actual; + final Subscriber<? super Long> downstream; RangeSubscription(Subscriber<? super Long> actual, long index, long end) { super(index, end); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { long f = end; - Subscriber<? super Long> a = actual; + Subscriber<? super Long> a = downstream; for (long i = index; i != f; i++) { if (cancelled) { @@ -141,7 +140,7 @@ void slowPath(long r) { long e = 0; long f = end; long i = index; - Subscriber<? super Long> a = actual; + Subscriber<? super Long> a = downstream; for (;;) { @@ -178,20 +177,19 @@ void slowPath(long r) { static final class RangeConditionalSubscription extends BaseRangeSubscription { - private static final long serialVersionUID = 2587302975077663557L; - final ConditionalSubscriber<? super Long> actual; + final ConditionalSubscriber<? super Long> downstream; RangeConditionalSubscription(ConditionalSubscriber<? super Long> actual, long index, long end) { super(index, end); - this.actual = actual; + this.downstream = actual; } @Override void fastPath() { long f = end; - ConditionalSubscriber<? super Long> a = actual; + ConditionalSubscriber<? super Long> a = downstream; for (long i = index; i != f; i++) { if (cancelled) { @@ -210,7 +208,7 @@ void slowPath(long r) { long e = 0; long f = end; long i = index; - ConditionalSubscriber<? super Long> a = actual; + ConditionalSubscriber<? super Long> a = downstream; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java index b83fffd4f6..67be1d18fd 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java @@ -48,7 +48,7 @@ static final class ReduceSubscriber<T> extends DeferredScalarSubscription<T> imp final BiFunction<T, T, T> reducer; - Subscription s; + Subscription upstream; ReduceSubscriber(Subscriber<? super T> actual, BiFunction<T, T, T> reducer) { super(actual); @@ -57,10 +57,10 @@ static final class ReduceSubscriber<T> extends DeferredScalarSubscription<T> imp @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -68,7 +68,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - if (s == SubscriptionHelper.CANCELLED) { + if (upstream == SubscriptionHelper.CANCELLED) { return; } @@ -80,7 +80,7 @@ public void onNext(T t) { value = ObjectHelper.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); } } @@ -88,34 +88,34 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (s == SubscriptionHelper.CANCELLED) { + if (upstream == SubscriptionHelper.CANCELLED) { RxJavaPlugins.onError(t); return; } - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - if (s == SubscriptionHelper.CANCELLED) { + if (upstream == SubscriptionHelper.CANCELLED) { return; } - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; T v = value; if (v != null) { complete(v); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void cancel() { super.cancel(); - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java index d6d4781033..569dcdccf8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java @@ -58,24 +58,24 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } static final class ReduceSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final BiFunction<T, T, T> reducer; T value; - Subscription s; + Subscription upstream; boolean done; ReduceSubscriber(MaybeObserver<? super T> actual, BiFunction<T, T, T> reducer) { - this.actual = actual; + this.downstream = actual; this.reducer = reducer; } @Override public void dispose() { - s.cancel(); + upstream.cancel(); done = true; } @@ -86,10 +86,10 @@ public boolean isDisposed() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -108,7 +108,7 @@ public void onNext(T t) { value = ObjectHelper.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); } } @@ -121,7 +121,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -133,12 +133,10 @@ public void onComplete() { T v = value; if (v != null) { // value = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } - - } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java index 32cf770953..bcd1042d48 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java @@ -21,6 +21,7 @@ import io.reactivex.functions.BiFunction; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; /** * Reduce a sequence of values, starting from a seed value and by using @@ -50,26 +51,26 @@ protected void subscribeActual(SingleObserver<? super R> observer) { static final class ReduceSeedObserver<T, R> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; final BiFunction<R, ? super T, R> reducer; R value; - Subscription s; + Subscription upstream; ReduceSeedObserver(SingleObserver<? super R> actual, BiFunction<R, ? super T, R> reducer, R value) { - this.actual = actual; + this.downstream = actual; this.value = value; this.reducer = reducer; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -78,39 +79,47 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T value) { R v = this.value; - try { - this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); - } catch (Throwable ex) { - Exceptions.throwIfFatal(ex); - s.cancel(); - onError(ex); + if (v != null) { + try { + this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + } } } @Override public void onError(Throwable e) { - value = null; - s = SubscriptionHelper.CANCELLED; - actual.onError(e); + if (value != null) { + value = null; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } } @Override public void onComplete() { R v = value; - value = null; - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(v); + if (v != null) { + value = null; + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(v); + } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java index 565c94221d..2da1306632 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java @@ -13,16 +13,18 @@ package io.reactivex.internal.operators.flowable; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.*; -import java.util.concurrent.locks.ReentrantLock; import org.reactivestreams.*; -import io.reactivex.FlowableSubscriber; -import io.reactivex.disposables.*; +import io.reactivex.*; +import io.reactivex.disposables.Disposable; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; /** * Returns an observable sequence that stays connected to the source as long as @@ -31,199 +33,239 @@ * @param <T> * the value type */ -public final class FlowableRefCount<T> extends AbstractFlowableWithUpstream<T, T> { +public final class FlowableRefCount<T> extends Flowable<T> { + final ConnectableFlowable<T> source; - volatile CompositeDisposable baseDisposable = new CompositeDisposable(); - final AtomicInteger subscriptionCount = new AtomicInteger(); - /** - * Use this lock for every subscription and disconnect action. - */ - final ReentrantLock lock = new ReentrantLock(); + final int n; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + RefConnection connection; + + public FlowableRefCount(ConnectableFlowable<T> source) { + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); + } + + public FlowableRefCount(ConnectableFlowable<T> source, int n, long timeout, TimeUnit unit, + Scheduler scheduler) { + this.source = source; + this.n = n; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } - final class ConnectionSubscriber - extends AtomicReference<Subscription> - implements FlowableSubscriber<T>, Subscription { + @Override + protected void subscribeActual(Subscriber<? super T> s) { - private static final long serialVersionUID = 152064694420235350L; - final Subscriber<? super T> subscriber; - final CompositeDisposable currentBase; - final Disposable resource; + RefConnection conn; - final AtomicLong requested; + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null) { + conn = new RefConnection(this); + connection = conn; + } - ConnectionSubscriber(Subscriber<? super T> subscriber, - CompositeDisposable currentBase, Disposable resource) { - this.subscriber = subscriber; - this.currentBase = currentBase; - this.resource = resource; - this.requested = new AtomicLong(); + long c = conn.subscriberCount; + if (c == 0L && conn.timer != null) { + conn.timer.dispose(); + } + conn.subscriberCount = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; + } } - @Override - public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this, requested, s); + source.subscribe(new RefCountSubscriber<T>(s, this, conn)); + + if (connect) { + source.connect(conn); } + } - @Override - public void onError(Throwable e) { - cleanup(); - subscriber.onError(e); + void cancel(RefConnection rc) { + SequentialDisposable sd; + synchronized (this) { + if (connection == null || connection != rc) { + return; + } + long c = rc.subscriberCount - 1; + rc.subscriberCount = c; + if (c != 0L || !rc.connected) { + return; + } + if (timeout == 0L) { + timeout(rc); + return; + } + sd = new SequentialDisposable(); + rc.timer = sd; } - @Override - public void onNext(T t) { - subscriber.onNext(t); + sd.replace(scheduler.scheduleDirect(rc, timeout, unit)); + } + + void terminated(RefConnection rc) { + synchronized (this) { + if (source instanceof FlowablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); + } + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } + } + } } + } - @Override - public void onComplete() { - cleanup(); - subscriber.onComplete(); + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; } + } - @Override - public void request(long n) { - SubscriptionHelper.deferredRequest(this, requested, n); + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); } + } - @Override - public void cancel() { - SubscriptionHelper.cancel(this); - resource.dispose(); - } - - void cleanup() { - // on error or completion we need to dispose the base CompositeDisposable - // and set the subscriptionCount to 0 - lock.lock(); - try { - if (baseDisposable == currentBase) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); + void timeout(RefConnection rc) { + synchronized (this) { + if (rc.subscriberCount == 0 && rc == connection) { + connection = null; + Disposable connectionObject = rc.get(); + DisposableHelper.dispose(rc); + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); } - baseDisposable.dispose(); - baseDisposable = new CompositeDisposable(); - subscriptionCount.set(0); } - } finally { - lock.unlock(); } } } - /** - * Constructor. - * - * @param source - * observable to apply ref count to - */ - public FlowableRefCount(ConnectableFlowable<T> source) { - super(source); - this.source = source; - } + static final class RefConnection extends AtomicReference<Disposable> + implements Runnable, Consumer<Disposable> { - @Override - public void subscribeActual(final Subscriber<? super T> subscriber) { - - lock.lock(); - if (subscriptionCount.incrementAndGet() == 1) { - - final AtomicBoolean writeLocked = new AtomicBoolean(true); - - try { - // need to use this overload of connect to ensure that - // baseSubscription is set in the case that source is a - // synchronous Observable - source.connect(onSubscribe(subscriber, writeLocked)); - } finally { - // need to cover the case where the source is subscribed to - // outside of this class thus preventing the Consumer passed - // to source.connect above being called - if (writeLocked.get()) { - // Consumer passed to source.connect was not called - lock.unlock(); + private static final long serialVersionUID = -4552101107598366241L; + + final FlowableRefCount<?> parent; + + Disposable timer; + + long subscriberCount; + + boolean connected; + + boolean disconnectedEarly; + + RefConnection(FlowableRefCount<?> parent) { + this.parent = parent; + } + + @Override + public void run() { + parent.timeout(this); + } + + @Override + public void accept(Disposable t) throws Exception { + DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); } } - } else { - try { - // ready to subscribe to source so do it - doSubscribe(subscriber, baseDisposable); - } finally { - // release the read lock - lock.unlock(); - } } - } - private Consumer<Disposable> onSubscribe(final Subscriber<? super T> subscriber, - final AtomicBoolean writeLocked) { - return new DisposeConsumer(subscriber, writeLocked); - } + static final class RefCountSubscriber<T> + extends AtomicBoolean implements FlowableSubscriber<T>, Subscription { - void doSubscribe(final Subscriber<? super T> subscriber, final CompositeDisposable currentBase) { - // handle disposing from the base subscription - Disposable d = disconnect(currentBase); + private static final long serialVersionUID = -7419642935409022375L; - ConnectionSubscriber connection = new ConnectionSubscriber(subscriber, currentBase, d); - subscriber.onSubscribe(connection); + final Subscriber<? super T> downstream; - source.subscribe(connection); - } + final FlowableRefCount<T> parent; - private Disposable disconnect(final CompositeDisposable current) { - return Disposables.fromRunnable(new DisposeTask(current)); - } + final RefConnection connection; - final class DisposeConsumer implements Consumer<Disposable> { - private final Subscriber<? super T> subscriber; - private final AtomicBoolean writeLocked; + Subscription upstream; - DisposeConsumer(Subscriber<? super T> subscriber, AtomicBoolean writeLocked) { - this.subscriber = subscriber; - this.writeLocked = writeLocked; + RefCountSubscriber(Subscriber<? super T> actual, FlowableRefCount<T> parent, RefConnection connection) { + this.downstream = actual; + this.parent = parent; + this.connection = connection; } @Override - public void accept(Disposable subscription) { - try { - baseDisposable.add(subscription); - // ready to subscribe to source so do it - doSubscribe(subscriber, baseDisposable); - } finally { - // release the write lock - lock.unlock(); - writeLocked.set(false); + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); } } - } - final class DisposeTask implements Runnable { - private final CompositeDisposable current; + @Override + public void onComplete() { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onComplete(); + } + } - DisposeTask(CompositeDisposable current) { - this.current = current; + @Override + public void request(long n) { + upstream.request(n); } @Override - public void run() { - lock.lock(); - try { - if (baseDisposable == current) { - if (subscriptionCount.decrementAndGet() == 0) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); - } - - baseDisposable.dispose(); - // need a new baseDisposable because once - // disposed stays that way - baseDisposable = new CompositeDisposable(); - } - } - } finally { - lock.unlock(); + public void cancel() { + upstream.cancel(); + if (compareAndSet(false, true)) { + parent.cancel(connection); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java index 1c61800b97..1bbdd7cd5c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java @@ -29,24 +29,26 @@ public FlowableRepeat(Flowable<T> source, long count) { @Override public void subscribeActual(Subscriber<? super T> s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RepeatSubscriber<T> rs = new RepeatSubscriber<T>(s, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sa, source); rs.subscribeNext(); } - // FIXME update to a fresh Rsc algorithm static final class RepeatSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T> { private static final long serialVersionUID = -7098360935104053232L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SubscriptionArbiter sa; final Publisher<? extends T> source; long remaining; + + long produced; + RepeatSubscriber(Subscriber<? super T> actual, long count, SubscriptionArbiter sa, Publisher<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.sa = sa; this.source = source; this.remaining = count; @@ -59,12 +61,13 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - actual.onNext(t); - sa.produced(1L); + produced++; + downstream.onNext(t); } + @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -76,7 +79,7 @@ public void onComplete() { if (r != 0L) { subscribeNext(); } else { - actual.onComplete(); + downstream.onComplete(); } } @@ -90,6 +93,11 @@ void subscribeNext() { if (sa.isCancelled()) { return; } + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } source.subscribe(this); missed = addAndGet(-missed); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java index 4ae29226f0..9c7057af59 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java @@ -31,24 +31,26 @@ public FlowableRepeatUntil(Flowable<T> source, BooleanSupplier until) { @Override public void subscribeActual(Subscriber<? super T> s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RepeatSubscriber<T> rs = new RepeatSubscriber<T>(s, until, sa, source); rs.subscribeNext(); } - // FIXME update to a fresh Rsc algorithm static final class RepeatSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T> { private static final long serialVersionUID = -7098360935104053232L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SubscriptionArbiter sa; final Publisher<? extends T> source; final BooleanSupplier stop; + + long produced; + RepeatSubscriber(Subscriber<? super T> actual, BooleanSupplier until, SubscriptionArbiter sa, Publisher<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.sa = sa; this.source = source; this.stop = until; @@ -61,12 +63,13 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - actual.onNext(t); - sa.produced(1L); + produced++; + downstream.onNext(t); } + @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -76,11 +79,11 @@ public void onComplete() { b = stop.getAsBoolean(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } if (b) { - actual.onComplete(); + downstream.onComplete(); } else { subscribeNext(); } @@ -93,6 +96,16 @@ void subscribeNext() { if (getAndIncrement() == 0) { int missed = 1; for (;;) { + if (sa.isCancelled()) { + return; + } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + source.subscribe(this); missed = addAndGet(-missed); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java index a95433a74d..b62254185d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java @@ -68,12 +68,11 @@ static final class WhenReceiver<T, U> extends AtomicInteger implements FlowableSubscriber<Object>, Subscription { - private static final long serialVersionUID = 2827772011130406689L; final Publisher<T> source; - final AtomicReference<Subscription> subscription; + final AtomicReference<Subscription> upstream; final AtomicLong requested; @@ -81,20 +80,20 @@ static final class WhenReceiver<T, U> WhenReceiver(Publisher<T> source) { this.source = source; - this.subscription = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.requested = new AtomicLong(); } @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(subscription, requested, s); + SubscriptionHelper.deferredSetOnce(upstream, requested, s); } @Override public void onNext(Object t) { if (getAndIncrement() == 0) { for (;;) { - if (SubscriptionHelper.isCancelled(subscription.get())) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { return; } @@ -110,23 +109,23 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { subscriber.cancel(); - subscriber.actual.onError(t); + subscriber.downstream.onError(t); } @Override public void onComplete() { subscriber.cancel(); - subscriber.actual.onComplete(); + subscriber.downstream.onComplete(); } @Override public void request(long n) { - SubscriptionHelper.deferredRequest(subscription, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(subscription); + SubscriptionHelper.cancel(upstream); } } @@ -134,7 +133,7 @@ abstract static class WhenSourceSubscriber<T, U> extends SubscriptionArbiter imp private static final long serialVersionUID = -5604623027276966720L; - protected final Subscriber<? super T> actual; + protected final Subscriber<? super T> downstream; protected final FlowableProcessor<U> processor; @@ -144,7 +143,8 @@ abstract static class WhenSourceSubscriber<T, U> extends SubscriptionArbiter imp WhenSourceSubscriber(Subscriber<? super T> actual, FlowableProcessor<U> processor, Subscription receiver) { - this.actual = actual; + super(false); + this.downstream = actual; this.processor = processor; this.receiver = receiver; } @@ -157,10 +157,11 @@ public final void onSubscribe(Subscription s) { @Override public final void onNext(T t) { produced++; - actual.onNext(t); + downstream.onNext(t); } protected final void again(U signal) { + setSubscription(EmptySubscription.INSTANCE); long p = produced; if (p != 0L) { produced = 0L; @@ -179,7 +180,6 @@ public final void cancel() { static final class RepeatWhenSubscriber<T> extends WhenSourceSubscriber<T, Object> { - private static final long serialVersionUID = -2680129890138081029L; RepeatWhenSubscriber(Subscriber<? super T> actual, FlowableProcessor<Object> processor, @@ -190,7 +190,7 @@ static final class RepeatWhenSubscriber<T> extends WhenSourceSubscriber<T, Objec @Override public void onError(Throwable t) { receiver.cancel(); - actual.onError(t); + downstream.onError(t); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java index fa6f494acf..196596f1b2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java @@ -24,6 +24,7 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.disposables.ResettableConnectable; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.fuseable.HasUpstreamPublisher; import io.reactivex.internal.subscribers.SubscriberResourceWrapper; @@ -32,7 +33,7 @@ import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Timed; -public final class FlowableReplay<T> extends ConnectableFlowable<T> implements HasUpstreamPublisher<T>, Disposable { +public final class FlowableReplay<T> extends ConnectableFlowable<T> implements HasUpstreamPublisher<T>, ResettableConnectable { /** The source observable. */ final Flowable<T> source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -57,20 +58,20 @@ public final class FlowableReplay<T> extends ConnectableFlowable<T> implements H public static <U, R> Flowable<R> multicastSelector( final Callable<? extends ConnectableFlowable<U>> connectableFactory, final Function<? super Flowable<U>, ? extends Publisher<R>> selector) { - return Flowable.unsafeCreate(new MultiCastPublisher<R, U>(connectableFactory, selector)); + return new MulticastFlowable<R, U>(connectableFactory, selector); } /** * Child Subscribers will observe the events of the ConnectableObservable on the * specified scheduler. * @param <T> the value type - * @param co the ConnectableFlowable to wrap + * @param cf the ConnectableFlowable to wrap * @param scheduler the target scheduler * @return the new ConnectableObservable instance */ - public static <T> ConnectableFlowable<T> observeOn(final ConnectableFlowable<T> co, final Scheduler scheduler) { - final Flowable<T> observable = co.observeOn(scheduler); - return RxJavaPlugins.onAssembly(new ConnectableFlowableReplay<T>(co, observable)); + public static <T> ConnectableFlowable<T> observeOn(final ConnectableFlowable<T> cf, final Scheduler scheduler) { + final Flowable<T> flowable = cf.observeOn(scheduler); + return RxJavaPlugins.onAssembly(new ConnectableFlowableReplay<T>(cf, flowable)); } /** @@ -161,15 +162,10 @@ protected void subscribeActual(Subscriber<? super T> s) { onSubscribe.subscribe(s); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public void dispose() { - current.lazySet(null); - } - - @Override - public boolean isDisposed() { - Disposable d = current.get(); - return d == null || d.isDisposed(); + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplaySubscriber)connectionObject, null); } @Override @@ -409,6 +405,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @SuppressWarnings("unchecked") @Override public void onComplete() { @@ -526,36 +523,16 @@ static final class InnerSubscription<T> extends AtomicLong implements Subscripti public void request(long n) { // ignore negative requests if (SubscriptionHelper.validate(n)) { - // In general, RxJava doesn't prevent concurrent requests (with each other or with - // a cancel) so we need a CAS-loop, but we need to handle - // request overflow and cancelled/not requested state as well. - for (;;) { - // get the current request amount - long r = get(); - // if child called cancel() do nothing - if (r == CANCELLED) { - return; - } - // ignore zero requests except any first that sets in zero - if (r >= 0L && n == 0) { - return; - } - // otherwise, increase the request count - long u = BackpressureHelper.addCap(r, n); - - // try setting the new request value - if (compareAndSet(r, u)) { - // increment the total request counter - BackpressureHelper.add(totalRequested, n); - // if successful, notify the parent dispatcher this child can receive more - // elements - parent.manageRequests(); - - parent.buffer.replay(this); - return; - } - // otherwise, someone else changed the state (perhaps a concurrent - // request or cancellation) so retry + // add to the current requested and cap it at MAX_VALUE + // except when there was a concurrent cancellation + if (BackpressureHelper.addCancel(this, n) != CANCELLED) { + // increment the total request counter + BackpressureHelper.add(totalRequested, n); + // if successful, notify the parent dispatcher this child can receive more + // elements + parent.manageRequests(); + // try replaying any cached content + parent.buffer.replay(this); } } } @@ -589,6 +566,8 @@ public void dispose() { // the others had non-zero. By removing this 'blocking' child, the others // are now free to receive events parent.manageRequests(); + // make sure the last known node is not retained + index = null; } } /** @@ -644,6 +623,7 @@ static final class UnboundedReplayBuffer<T> extends ArrayList<Object> implements UnboundedReplayBuffer(int capacityHint) { super(capacityHint); } + @Override public void next(T value) { add(NotificationLite.next(value)); @@ -793,6 +773,11 @@ final void removeFirst() { } setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } } /** * Arranges the given node is the new head from now on. @@ -826,6 +811,15 @@ public final void complete() { truncateFinal(); } + final void trimHead() { + Node head = get(); + if (head.value != null) { + Node n = new Node(null, 0L); + n.lazySet(head.get()); + set(n); + } + } + @Override public final void replay(InnerSubscription<T> output) { synchronized (output) { @@ -837,6 +831,7 @@ public final void replay(InnerSubscription<T> output) { } for (;;) { if (output.isDisposed()) { + output.index = null; return; } @@ -877,6 +872,7 @@ public final void replay(InnerSubscription<T> output) { break; } if (output.isDisposed()) { + output.index = null; return; } } @@ -929,7 +925,7 @@ void truncate() { * based on its properties (i.e., truncate but the very last node). */ void truncateFinal() { - + trimHead(); } /* test */ final void collect(Collection<? super T> output) { Node n = getHead(); @@ -1024,7 +1020,7 @@ void truncate() { int e = 0; for (;;) { if (next != null) { - if (size > limit) { + if (size > limit && size > 1) { // never truncate the very last item just added e++; size--; prev = next; @@ -1048,6 +1044,7 @@ void truncate() { setFirst(prev); } } + @Override void truncateFinal() { long timeLimit = scheduler.now(unit) - maxAge; @@ -1100,20 +1097,20 @@ Node getHead() { } } - static final class MultiCastPublisher<R, U> implements Publisher<R> { + static final class MulticastFlowable<R, U> extends Flowable<R> { private final Callable<? extends ConnectableFlowable<U>> connectableFactory; private final Function<? super Flowable<U>, ? extends Publisher<R>> selector; - MultiCastPublisher(Callable<? extends ConnectableFlowable<U>> connectableFactory, Function<? super Flowable<U>, ? extends Publisher<R>> selector) { + MulticastFlowable(Callable<? extends ConnectableFlowable<U>> connectableFactory, Function<? super Flowable<U>, ? extends Publisher<R>> selector) { this.connectableFactory = connectableFactory; this.selector = selector; } @Override - public void subscribe(Subscriber<? super R> child) { - ConnectableFlowable<U> co; + protected void subscribeActual(Subscriber<? super R> child) { + ConnectableFlowable<U> cf; try { - co = ObjectHelper.requireNonNull(connectableFactory.call(), "The connectableFactory returned null"); + cf = ObjectHelper.requireNonNull(connectableFactory.call(), "The connectableFactory returned null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); EmptySubscription.error(e, child); @@ -1122,7 +1119,7 @@ public void subscribe(Subscriber<? super R> child) { Publisher<R> observable; try { - observable = ObjectHelper.requireNonNull(selector.apply(co), "The selector returned a null Publisher"); + observable = ObjectHelper.requireNonNull(selector.apply(cf), "The selector returned a null Publisher"); } catch (Throwable e) { Exceptions.throwIfFatal(e); EmptySubscription.error(e, child); @@ -1133,7 +1130,7 @@ public void subscribe(Subscriber<? super R> child) { observable.subscribe(srw); - co.connect(new DisposableConsumer(srw)); + cf.connect(new DisposableConsumer(srw)); } final class DisposableConsumer implements Consumer<Disposable> { @@ -1151,22 +1148,22 @@ public void accept(Disposable r) { } static final class ConnectableFlowableReplay<T> extends ConnectableFlowable<T> { - private final ConnectableFlowable<T> co; - private final Flowable<T> observable; + private final ConnectableFlowable<T> cf; + private final Flowable<T> flowable; - ConnectableFlowableReplay(ConnectableFlowable<T> co, Flowable<T> observable) { - this.co = co; - this.observable = observable; + ConnectableFlowableReplay(ConnectableFlowable<T> cf, Flowable<T> flowable) { + this.cf = cf; + this.flowable = flowable; } @Override public void connect(Consumer<? super Disposable> connection) { - co.connect(connection); + cf.connect(connection); } @Override protected void subscribeActual(Subscriber<? super T> s) { - observable.subscribe(s); + flowable.subscribe(s); } } @@ -1226,7 +1223,8 @@ public void subscribe(Subscriber<? super T> child) { buf = bufferFactory.call(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - throw ExceptionHelper.wrapOrThrow(ex); + EmptySubscription.error(ex, child); + return; } // create a new subscriber to source ReplaySubscriber<T> u = new ReplaySubscriber<T>(buf); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java index b478f408ea..8bc9ba27d0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java @@ -33,26 +33,28 @@ public FlowableRetryBiPredicate( @Override public void subscribeActual(Subscriber<? super T> s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); RetryBiSubscriber<T> rs = new RetryBiSubscriber<T>(s, predicate, sa, source); rs.subscribeNext(); } - // FIXME update to a fresh Rsc algorithm static final class RetryBiSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T> { private static final long serialVersionUID = -7098360935104053232L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SubscriptionArbiter sa; final Publisher<? extends T> source; final BiPredicate<? super Integer, ? super Throwable> predicate; int retries; + + long produced; + RetryBiSubscriber(Subscriber<? super T> actual, BiPredicate<? super Integer, ? super Throwable> predicate, SubscriptionArbiter sa, Publisher<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.sa = sa; this.source = source; this.predicate = predicate; @@ -65,9 +67,10 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - actual.onNext(t); - sa.produced(1L); + produced++; + downstream.onNext(t); } + @Override public void onError(Throwable t) { boolean b; @@ -75,11 +78,11 @@ public void onError(Throwable t) { b = predicate.test(++retries, t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (!b) { - actual.onError(t); + downstream.onError(t); return; } subscribeNext(); @@ -87,7 +90,7 @@ public void onError(Throwable t) { @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } /** @@ -100,6 +103,13 @@ void subscribeNext() { if (sa.isCancelled()) { return; } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + source.subscribe(this); missed = addAndGet(-missed); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java index 2b2c13f0a1..8d035aaf95 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java @@ -35,26 +35,28 @@ public FlowableRetryPredicate(Flowable<T> source, @Override public void subscribeActual(Subscriber<? super T> s) { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(false); s.onSubscribe(sa); - RepeatSubscriber<T> rs = new RepeatSubscriber<T>(s, count, predicate, sa, source); + RetrySubscriber<T> rs = new RetrySubscriber<T>(s, count, predicate, sa, source); rs.subscribeNext(); } - // FIXME update to a fresh Rsc algorithm - static final class RepeatSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T> { + static final class RetrySubscriber<T> extends AtomicInteger implements FlowableSubscriber<T> { private static final long serialVersionUID = -7098360935104053232L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SubscriptionArbiter sa; final Publisher<? extends T> source; final Predicate<? super Throwable> predicate; long remaining; - RepeatSubscriber(Subscriber<? super T> actual, long count, + + long produced; + + RetrySubscriber(Subscriber<? super T> actual, long count, Predicate<? super Throwable> predicate, SubscriptionArbiter sa, Publisher<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.sa = sa; this.source = source; this.predicate = predicate; @@ -68,9 +70,10 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - actual.onNext(t); - sa.produced(1L); + produced++; + downstream.onNext(t); } + @Override public void onError(Throwable t) { long r = remaining; @@ -78,18 +81,18 @@ public void onError(Throwable t) { remaining = r - 1; } if (r == 0) { - actual.onError(t); + downstream.onError(t); } else { boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (!b) { - actual.onError(t); + downstream.onError(t); return; } subscribeNext(); @@ -98,7 +101,7 @@ public void onError(Throwable t) { @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } /** @@ -111,6 +114,13 @@ void subscribeNext() { if (sa.isCancelled()) { return; } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + source.subscribe(this); missed = addAndGet(-missed); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java index 27785e5075..de0f735802 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java @@ -64,7 +64,6 @@ public void subscribeActual(Subscriber<? super T> s) { static final class RetryWhenSubscriber<T> extends WhenSourceSubscriber<T, Throwable> { - private static final long serialVersionUID = -2680129890138081029L; RetryWhenSubscriber(Subscriber<? super T> actual, FlowableProcessor<Throwable> processor, @@ -80,7 +79,7 @@ public void onError(Throwable t) { @Override public void onComplete() { receiver.cancel(); - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java index de9a71b223..66b9c48ec3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java @@ -49,25 +49,25 @@ abstract static class SamplePublisherSubscriber<T> extends AtomicReference<T> im private static final long serialVersionUID = -3517602651313910099L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Publisher<?> sampler; final AtomicLong requested = new AtomicLong(); final AtomicReference<Subscription> other = new AtomicReference<Subscription>(); - Subscription s; + Subscription upstream; SamplePublisherSubscriber(Subscriber<? super T> actual, Publisher<?> other) { - this.actual = actual; + this.downstream = actual; this.sampler = other; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); if (other.get() == null) { sampler.subscribe(new SamplerSubscriber<T>(this)); s.request(Long.MAX_VALUE); @@ -84,17 +84,17 @@ public void onNext(T t) { @Override public void onError(Throwable t) { SubscriptionHelper.cancel(other); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { SubscriptionHelper.cancel(other); - completeMain(); + completion(); } - boolean setOther(Subscription o) { - return SubscriptionHelper.setOnce(other, o); + void setOther(Subscription o) { + SubscriptionHelper.setOnce(other, o, Long.MAX_VALUE); } @Override @@ -107,17 +107,17 @@ public void request(long n) { @Override public void cancel() { SubscriptionHelper.cancel(other); - s.cancel(); + upstream.cancel(); } public void error(Throwable e) { - s.cancel(); - actual.onError(e); + upstream.cancel(); + downstream.onError(e); } public void complete() { - s.cancel(); - completeOther(); + upstream.cancel(); + completion(); } void emit() { @@ -125,18 +125,16 @@ void emit() { if (value != null) { long r = requested.get(); if (r != 0L) { - actual.onNext(value); + downstream.onNext(value); BackpressureHelper.produced(requested, 1); } else { cancel(); - actual.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); + downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); } } } - abstract void completeMain(); - - abstract void completeOther(); + abstract void completion(); abstract void run(); } @@ -150,9 +148,7 @@ static final class SamplerSubscriber<T> implements FlowableSubscriber<Object> { @Override public void onSubscribe(Subscription s) { - if (parent.setOther(s)) { - s.request(Long.MAX_VALUE); - } + parent.setOther(s); } @Override @@ -180,13 +176,8 @@ static final class SampleMainNoLast<T> extends SamplePublisherSubscriber<T> { } @Override - void completeMain() { - actual.onComplete(); - } - - @Override - void completeOther() { - actual.onComplete(); + void completion() { + downstream.onComplete(); } @Override @@ -209,20 +200,11 @@ static final class SampleMainEmitLast<T> extends SamplePublisherSubscriber<T> { } @Override - void completeMain() { - done = true; - if (wip.getAndIncrement() == 0) { - emit(); - actual.onComplete(); - } - } - - @Override - void completeOther() { + void completion() { done = true; if (wip.getAndIncrement() == 0) { emit(); - actual.onComplete(); + downstream.onComplete(); } } @@ -233,7 +215,7 @@ void run() { boolean d = done; emit(); if (d) { - actual.onComplete(); + downstream.onComplete(); return; } } while (wip.decrementAndGet() != 0); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java index 4308b99165..47ed31f052 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java @@ -54,7 +54,7 @@ abstract static class SampleTimedSubscriber<T> extends AtomicReference<T> implem private static final long serialVersionUID = -3517602651313910099L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long period; final TimeUnit unit; final Scheduler scheduler; @@ -63,10 +63,10 @@ abstract static class SampleTimedSubscriber<T> extends AtomicReference<T> implem final SequentialDisposable timer = new SequentialDisposable(); - Subscription s; + Subscription upstream; SampleTimedSubscriber(Subscriber<? super T> actual, long period, TimeUnit unit, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.period = period; this.unit = unit; this.scheduler = scheduler; @@ -74,9 +74,9 @@ abstract static class SampleTimedSubscriber<T> extends AtomicReference<T> implem @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); timer.replace(scheduler.schedulePeriodicallyDirect(this, period, period, unit)); s.request(Long.MAX_VALUE); } @@ -90,7 +90,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { cancelTimer(); - actual.onError(t); + downstream.onError(t); } @Override @@ -113,7 +113,7 @@ public void request(long n) { @Override public void cancel() { cancelTimer(); - s.cancel(); + upstream.cancel(); } void emit() { @@ -121,11 +121,11 @@ void emit() { if (value != null) { long r = requested.get(); if (r != 0L) { - actual.onNext(value); + downstream.onNext(value); BackpressureHelper.produced(requested, 1); } else { cancel(); - actual.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); + downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); } } } @@ -143,7 +143,7 @@ static final class SampleTimedNoLast<T> extends SampleTimedSubscriber<T> { @Override void complete() { - actual.onComplete(); + downstream.onComplete(); } @Override @@ -167,7 +167,7 @@ static final class SampleTimedEmitLast<T> extends SampleTimedSubscriber<T> { void complete() { emit(); if (wip.decrementAndGet() == 0) { - actual.onComplete(); + downstream.onComplete(); } } @@ -176,7 +176,7 @@ public void run() { if (wip.incrementAndGet() == 2) { emit(); if (wip.decrementAndGet() == 0) { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java index 91bb5cc274..0c5aca6bf6 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java @@ -35,25 +35,25 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class ScanSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final BiFunction<T, T, T> accumulator; - Subscription s; + Subscription upstream; T value; boolean done; ScanSubscriber(Subscriber<? super T> actual, BiFunction<T, T, T> accumulator) { - this.actual = actual; + this.downstream = actual; this.accumulator = accumulator; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -62,7 +62,7 @@ public void onNext(T t) { if (done) { return; } - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; T v = value; if (v == null) { value = t; @@ -74,7 +74,7 @@ public void onNext(T t) { u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The value returned by the accumulator is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } @@ -91,7 +91,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -100,17 +100,17 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java index 19b4f3fc68..6586b55253 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java @@ -57,7 +57,7 @@ static final class ScanSeedSubscriber<T, R> implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -1776795561228106469L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final BiFunction<R, ? super T, R> accumulator; @@ -74,14 +74,14 @@ static final class ScanSeedSubscriber<T, R> volatile boolean done; Throwable error; - Subscription s; + Subscription upstream; R value; int consumed; ScanSeedSubscriber(Subscriber<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value, int prefetch) { - this.actual = actual; + this.downstream = actual; this.accumulator = accumulator; this.value = value; this.prefetch = prefetch; @@ -93,10 +93,10 @@ static final class ScanSeedSubscriber<T, R> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch - 1); } @@ -113,7 +113,7 @@ public void onNext(T t) { v = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); onError(ex); return; } @@ -146,7 +146,7 @@ public void onComplete() { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { queue.clear(); } @@ -166,7 +166,7 @@ void drain() { } int missed = 1; - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; SimplePlainQueue<R> q = queue; int lim = limit; int c = consumed; @@ -209,7 +209,7 @@ void drain() { e++; if (++c == lim) { c = 0; - s.request(lim); + upstream.request(lim); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java index be145c3499..fd10367248 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java @@ -132,7 +132,7 @@ public void drain() { if (ex != null) { cancelAndClear(); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } @@ -146,7 +146,7 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } v1 = a; @@ -162,7 +162,7 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } v2 = b; @@ -192,7 +192,7 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } @@ -220,7 +220,7 @@ public void drain() { if (ex != null) { cancelAndClear(); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java index e9c88c7c7c..bcda903c85 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java @@ -42,9 +42,9 @@ public FlowableSequenceEqualSingle(Publisher<? extends T> first, Publisher<? ext } @Override - public void subscribeActual(SingleObserver<? super Boolean> s) { - EqualCoordinator<T> parent = new EqualCoordinator<T>(s, prefetch, comparer); - s.onSubscribe(parent); + public void subscribeActual(SingleObserver<? super Boolean> observer) { + EqualCoordinator<T> parent = new EqualCoordinator<T>(observer, prefetch, comparer); + observer.onSubscribe(parent); parent.subscribe(first, second); } @@ -59,7 +59,7 @@ static final class EqualCoordinator<T> private static final long serialVersionUID = -6178010334400373240L; - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final BiPredicate<? super T, ? super T> comparer; @@ -74,7 +74,7 @@ static final class EqualCoordinator<T> T v2; EqualCoordinator(SingleObserver<? super Boolean> actual, int prefetch, BiPredicate<? super T, ? super T> comparer) { - this.actual = actual; + this.downstream = actual; this.comparer = comparer; this.first = new EqualSubscriber<T>(this, prefetch); this.second = new EqualSubscriber<T>(this, prefetch); @@ -98,7 +98,7 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(first.get()); + return first.get() == SubscriptionHelper.CANCELLED; } void cancelAndClear() { @@ -132,7 +132,7 @@ public void drain() { if (ex != null) { cancelAndClear(); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } @@ -146,7 +146,7 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } v1 = a; @@ -162,7 +162,7 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } v2 = b; @@ -171,12 +171,12 @@ public void drain() { boolean e2 = b == null; if (d1 && d2 && e1 && e2) { - actual.onSuccess(true); + downstream.onSuccess(true); return; } if ((d1 && d2) && (e1 != e2)) { cancelAndClear(); - actual.onSuccess(false); + downstream.onSuccess(false); return; } @@ -192,13 +192,13 @@ public void drain() { Exceptions.throwIfFatal(exc); cancelAndClear(); error.addThrowable(exc); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } if (!c) { cancelAndClear(); - actual.onSuccess(false); + downstream.onSuccess(false); return; } @@ -220,7 +220,7 @@ public void drain() { if (ex != null) { cancelAndClear(); - actual.onError(error.terminate()); + downstream.onError(error.terminate()); return; } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java index a18505404f..7b4ace56f0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java @@ -13,6 +13,8 @@ package io.reactivex.internal.operators.flowable; +import java.util.NoSuchElementException; + import org.reactivestreams.*; import io.reactivex.*; @@ -23,14 +25,17 @@ public final class FlowableSingle<T> extends AbstractFlowableWithUpstream<T, T> final T defaultValue; - public FlowableSingle(Flowable<T> source, T defaultValue) { + final boolean failOnEmpty; + + public FlowableSingle(Flowable<T> source, T defaultValue, boolean failOnEmpty) { super(source); this.defaultValue = defaultValue; + this.failOnEmpty = failOnEmpty; } @Override protected void subscribeActual(Subscriber<? super T> s) { - source.subscribe(new SingleElementSubscriber<T>(s, defaultValue)); + source.subscribe(new SingleElementSubscriber<T>(s, defaultValue, failOnEmpty)); } static final class SingleElementSubscriber<T> extends DeferredScalarSubscription<T> @@ -40,20 +45,23 @@ static final class SingleElementSubscriber<T> extends DeferredScalarSubscription final T defaultValue; - Subscription s; + final boolean failOnEmpty; + + Subscription upstream; boolean done; - SingleElementSubscriber(Subscriber<? super T> actual, T defaultValue) { + SingleElementSubscriber(Subscriber<? super T> actual, T defaultValue, boolean failOnEmpty) { super(actual); this.defaultValue = defaultValue; + this.failOnEmpty = failOnEmpty; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -65,8 +73,8 @@ public void onNext(T t) { } if (value != null) { done = true; - s.cancel(); - actual.onError(new IllegalArgumentException("Sequence contains more than one element!")); + upstream.cancel(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); return; } value = t; @@ -79,7 +87,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -94,7 +102,11 @@ public void onComplete() { v = defaultValue; } if (v == null) { - actual.onComplete(); + if (failOnEmpty) { + downstream.onError(new NoSuchElementException()); + } else { + downstream.onComplete(); + } } else { complete(v); } @@ -103,7 +115,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java index 20608cd430..14be53915a 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java @@ -30,35 +30,35 @@ public FlowableSingleMaybe(Flowable<T> source) { } @Override - protected void subscribeActual(MaybeObserver<? super T> s) { - source.subscribe(new SingleElementSubscriber<T>(s)); + protected void subscribeActual(MaybeObserver<? super T> observer) { + source.subscribe(new SingleElementSubscriber<T>(observer)); } @Override public Flowable<T> fuseToFlowable() { - return RxJavaPlugins.onAssembly(new FlowableSingle<T>(source, null)); + return RxJavaPlugins.onAssembly(new FlowableSingle<T>(source, null, false)); } static final class SingleElementSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Subscription s; + Subscription upstream; boolean done; T value; - SingleElementSubscriber(MaybeObserver<? super T> actual) { - this.actual = actual; + SingleElementSubscriber(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -70,9 +70,9 @@ public void onNext(T t) { } if (value != null) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onError(new IllegalArgumentException("Sequence contains more than one element!")); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); return; } value = t; @@ -85,8 +85,8 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override @@ -95,25 +95,25 @@ public void onComplete() { return; } done = true; - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; T v = value; value = null; if (v == null) { - actual.onComplete(); + downstream.onComplete(); } else { - actual.onSuccess(v); + downstream.onSuccess(v); } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java index 44df823067..cb4bce53c5 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java @@ -35,38 +35,38 @@ public FlowableSingleSingle(Flowable<T> source, T defaultValue) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - source.subscribe(new SingleElementSubscriber<T>(s, defaultValue)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new SingleElementSubscriber<T>(observer, defaultValue)); } @Override public Flowable<T> fuseToFlowable() { - return RxJavaPlugins.onAssembly(new FlowableSingle<T>(source, defaultValue)); + return RxJavaPlugins.onAssembly(new FlowableSingle<T>(source, defaultValue, true)); } static final class SingleElementSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final T defaultValue; - Subscription s; + Subscription upstream; boolean done; T value; SingleElementSubscriber(SingleObserver<? super T> actual, T defaultValue) { - this.actual = actual; + this.downstream = actual; this.defaultValue = defaultValue; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -78,9 +78,9 @@ public void onNext(T t) { } if (value != null) { done = true; - s.cancel(); - s = SubscriptionHelper.CANCELLED; - actual.onError(new IllegalArgumentException("Sequence contains more than one element!")); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); return; } value = t; @@ -93,8 +93,8 @@ public void onError(Throwable t) { return; } done = true; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override @@ -103,7 +103,7 @@ public void onComplete() { return; } done = true; - s = SubscriptionHelper.CANCELLED; + upstream = SubscriptionHelper.CANCELLED; T v = value; value = null; if (v == null) { @@ -111,21 +111,21 @@ public void onComplete() { } if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java index 6957ff66c3..58a0446b98 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java @@ -31,22 +31,22 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class SkipSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; long remaining; - Subscription s; + Subscription upstream; SkipSubscriber(Subscriber<? super T> actual, long n) { - this.actual = actual; + this.downstream = actual; this.remaining = n; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { long n = remaining; - this.s = s; - actual.onSubscribe(this); + this.upstream = s; + downstream.onSubscribe(this); s.request(n); } } @@ -56,28 +56,28 @@ public void onNext(T t) { if (remaining != 0L) { remaining--; } else { - actual.onNext(t); + downstream.onNext(t); } } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java index a0ed02a567..bbda349db2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java @@ -36,53 +36,53 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class SkipLastSubscriber<T> extends ArrayDeque<T> implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -3807491841935125653L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final int skip; - Subscription s; + Subscription upstream; SkipLastSubscriber(Subscriber<? super T> actual, int skip) { super(skip); - this.actual = actual; + this.downstream = actual; this.skip = skip; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (skip == size()) { - actual.onNext(poll()); + downstream.onNext(poll()); } else { - s.request(1); + upstream.request(1); } offer(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java index 5cad43dd87..0ffd249f15 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java @@ -47,14 +47,14 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class SkipLastTimedSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -5677354903406201275L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long time; final TimeUnit unit; final Scheduler scheduler; final SpscLinkedArrayQueue<Object> queue; final boolean delayError; - Subscription s; + Subscription upstream; final AtomicLong requested = new AtomicLong(); @@ -64,7 +64,7 @@ static final class SkipLastTimedSubscriber<T> extends AtomicInteger implements F Throwable error; SkipLastTimedSubscriber(Subscriber<? super T> actual, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.time = time; this.unit = unit; this.scheduler = scheduler; @@ -74,9 +74,9 @@ static final class SkipLastTimedSubscriber<T> extends AtomicInteger implements F @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -115,7 +115,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { queue.clear(); @@ -130,7 +130,7 @@ void drain() { int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; final boolean delayError = this.delayError; final TimeUnit unit = this.unit; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java index 857623d1ac..0324c720a2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java @@ -43,9 +43,9 @@ static final class SkipUntilMainSubscriber<T> extends AtomicInteger implements ConditionalSubscriber<T>, Subscription { private static final long serialVersionUID = -6270983465606289181L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; final AtomicLong requested; @@ -55,9 +55,9 @@ static final class SkipUntilMainSubscriber<T> extends AtomicInteger volatile boolean gate; - SkipUntilMainSubscriber(Subscriber<? super T> actual) { - this.actual = actual; - this.s = new AtomicReference<Subscription>(); + SkipUntilMainSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference<Subscription>(); this.requested = new AtomicLong(); this.other = new OtherSubscriber(); this.error = new AtomicThrowable(); @@ -65,20 +65,20 @@ static final class SkipUntilMainSubscriber<T> extends AtomicInteger @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.get().request(1); + upstream.get().request(1); } } @Override public boolean tryOnNext(T t) { if (gate) { - HalfSerializer.onNext(actual, t, this, error); + HalfSerializer.onNext(downstream, t, this, error); return true; } return false; @@ -87,23 +87,23 @@ public boolean tryOnNext(T t) { @Override public void onError(Throwable t) { SubscriptionHelper.cancel(other); - HalfSerializer.onError(actual, t, SkipUntilMainSubscriber.this, error); + HalfSerializer.onError(downstream, t, SkipUntilMainSubscriber.this, error); } @Override public void onComplete() { SubscriptionHelper.cancel(other); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } @Override public void request(long n) { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); SubscriptionHelper.cancel(other); } @@ -114,9 +114,7 @@ final class OtherSubscriber extends AtomicReference<Subscription> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -127,8 +125,8 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - SubscriptionHelper.cancel(s); - HalfSerializer.onError(actual, t, SkipUntilMainSubscriber.this, error); + SubscriptionHelper.cancel(upstream); + HalfSerializer.onError(downstream, t, SkipUntilMainSubscriber.this, error); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java index 3a2ba35df3..8123be6782 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java @@ -33,64 +33,64 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class SkipWhileSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean notSkipping; SkipWhileSubscriber(Subscriber<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (notSkipping) { - actual.onNext(t); + downstream.onNext(t); } else { boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); - actual.onError(e); + upstream.cancel(); + downstream.onError(e); return; } if (b) { - s.request(1); + upstream.request(1); } else { notSkipping = true; - actual.onNext(t); + downstream.onNext(t); } } } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java index 1bb3f1e829..f603aca819 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java @@ -53,11 +53,11 @@ static final class SubscribeOnSubscriber<T> extends AtomicReference<Thread> private static final long serialVersionUID = 8094547886072529208L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Scheduler.Worker worker; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; final AtomicLong requested; @@ -66,10 +66,10 @@ static final class SubscribeOnSubscriber<T> extends AtomicReference<Thread> Publisher<T> source; SubscribeOnSubscriber(Subscriber<? super T> actual, Scheduler.Worker worker, Publisher<T> source, boolean requestOn) { - this.actual = actual; + this.downstream = actual; this.worker = worker; this.source = source; - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.requested = new AtomicLong(); this.nonScheduledRequests = !requestOn; } @@ -84,7 +84,7 @@ public void run() { @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { long r = requested.getAndSet(0L); if (r != 0L) { requestUpstream(r, s); @@ -94,30 +94,30 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); worker.dispose(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); worker.dispose(); } @Override public void request(final long n) { if (SubscriptionHelper.validate(n)) { - Subscription s = this.s.get(); + Subscription s = this.upstream.get(); if (s != null) { requestUpstream(n, s); } else { BackpressureHelper.add(requested, n); - s = this.s.get(); + s = this.upstream.get(); if (s != null) { long r = requested.getAndSet(0L); if (r != 0L) { @@ -138,22 +138,22 @@ void requestUpstream(final long n, final Subscription s) { @Override public void cancel() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); worker.dispose(); } static final class Request implements Runnable { - private final Subscription s; - private final long n; + final Subscription upstream; + final long n; Request(Subscription s, long n) { - this.s = s; + this.upstream = s; this.n = n; } @Override public void run() { - s.request(n); + upstream.request(n); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java index 33f215bf3d..be8febdf17 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java @@ -33,17 +33,17 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class SwitchIfEmptySubscriber<T> implements FlowableSubscriber<T> { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Publisher<? extends T> other; final SubscriptionArbiter arbiter; boolean empty; SwitchIfEmptySubscriber(Subscriber<? super T> actual, Publisher<? extends T> other) { - this.actual = actual; + this.downstream = actual; this.other = other; this.empty = true; - this.arbiter = new SubscriptionArbiter(); + this.arbiter = new SubscriptionArbiter(false); } @Override @@ -56,12 +56,12 @@ public void onNext(T t) { if (empty) { empty = false; } - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -70,7 +70,7 @@ public void onComplete() { empty = false; other.subscribe(this); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java index 03ddb6c0ce..caf8c235c4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java @@ -52,18 +52,17 @@ protected void subscribeActual(Subscriber<? super R> s) { static final class SwitchMapSubscriber<T, R> extends AtomicInteger implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -3491074160481096299L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends Publisher<? extends R>> mapper; final int bufferSize; final boolean delayErrors; - volatile boolean done; final AtomicThrowable error; volatile boolean cancelled; - Subscription s; + Subscription upstream; final AtomicReference<SwitchMapInnerSubscriber<T, R>> active = new AtomicReference<SwitchMapInnerSubscriber<T, R>>(); @@ -80,7 +79,7 @@ static final class SwitchMapSubscriber<T, R> extends AtomicInteger implements Fl SwitchMapSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends Publisher<? extends R>> mapper, int bufferSize, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.bufferSize = bufferSize; this.delayErrors = delayErrors; @@ -89,9 +88,9 @@ static final class SwitchMapSubscriber<T, R> extends AtomicInteger implements Fl @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -114,7 +113,7 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(mapper.apply(t), "The publisher returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } @@ -160,7 +159,7 @@ public void request(long n) { if (SubscriptionHelper.validate(n)) { BackpressureHelper.add(requested, n); if (unique == 0L) { - s.request(Long.MAX_VALUE); + upstream.request(Long.MAX_VALUE); } else { drain(); } @@ -171,7 +170,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); disposeInner(); } @@ -193,14 +192,13 @@ void drain() { return; } - final Subscriber<? super R> a = actual; + final Subscriber<? super R> a = downstream; int missing = 1; for (;;) { if (cancelled) { - active.lazySet(null); return; } @@ -315,7 +313,7 @@ void drain() { if (r != Long.MAX_VALUE) { requested.addAndGet(-e); } - inner.get().request(e); + inner.request(e); } } @@ -359,7 +357,7 @@ public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") QueueSubscription<R> qs = (QueueSubscription<R>) s; - int m = qs.requestFusion(QueueSubscription.ANY); + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { fusionMode = m; queue = qs; @@ -398,7 +396,8 @@ public void onError(Throwable t) { SwitchMapSubscriber<T, R> p = parent; if (index == p.unique && p.error.addThrowable(t)) { if (!p.delayErrors) { - p.s.cancel(); + p.upstream.cancel(); + p.done = true; } done = true; p.drain(); @@ -419,5 +418,11 @@ public void onComplete() { public void cancel() { SubscriptionHelper.cancel(this); } + + public void request(long n) { + if (fusionMode != QueueSubscription.SYNC) { + get().request(n); + } + } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java index 67c2a49c21..5d33c14523 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java @@ -19,6 +19,7 @@ import io.reactivex.*; import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; public final class FlowableTake<T> extends AbstractFlowableWithUpstream<T, T> { final long limit; @@ -35,55 +36,68 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class TakeSubscriber<T> extends AtomicBoolean implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -5636543848937116287L; - boolean done; - Subscription subscription; - final Subscriber<? super T> actual; + + final Subscriber<? super T> downstream; + final long limit; + + boolean done; + + Subscription upstream; + long remaining; + TakeSubscriber(Subscriber<? super T> actual, long limit) { - this.actual = actual; + this.downstream = actual; this.limit = limit; this.remaining = limit; } + @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.subscription, s)) { - subscription = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + upstream = s; if (limit == 0L) { s.cancel(); done = true; - EmptySubscription.complete(actual); + EmptySubscription.complete(downstream); } else { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } } + @Override public void onNext(T t) { if (!done && remaining-- > 0) { boolean stop = remaining == 0; - actual.onNext(t); + downstream.onNext(t); if (stop) { - subscription.cancel(); + upstream.cancel(); onComplete(); } } } + @Override public void onError(Throwable t) { if (!done) { done = true; - subscription.cancel(); - actual.onError(t); + upstream.cancel(); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); } } + @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } + @Override public void request(long n) { if (!SubscriptionHelper.validate(n)) { @@ -91,15 +105,16 @@ public void request(long n) { } if (!get() && compareAndSet(false, true)) { if (n >= limit) { - subscription.request(Long.MAX_VALUE); + upstream.request(Long.MAX_VALUE); return; } } - subscription.request(n); + upstream.request(n); } + @Override public void cancel() { - subscription.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java index 4ff36f5c56..60318d0b08 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java @@ -38,10 +38,10 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class TakeLastSubscriber<T> extends ArrayDeque<T> implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = 7240042530241604978L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final int count; - Subscription s; + Subscription upstream; volatile boolean done; volatile boolean cancelled; @@ -50,15 +50,15 @@ static final class TakeLastSubscriber<T> extends ArrayDeque<T> implements Flowab final AtomicInteger wip = new AtomicInteger(); TakeLastSubscriber(Subscriber<? super T> actual, int count) { - this.actual = actual; + this.downstream = actual; this.count = count; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -73,7 +73,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -93,12 +93,12 @@ public void request(long n) { @Override public void cancel() { cancelled = true; - s.cancel(); + upstream.cancel(); } void drain() { if (wip.getAndIncrement() == 0) { - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; long r = requested.get(); do { if (cancelled) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java index 1372bd874e..570bf3c823 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java @@ -33,17 +33,17 @@ static final class TakeLastOneSubscriber<T> extends DeferredScalarSubscription<T private static final long serialVersionUID = -5467847744262967226L; - Subscription s; + Subscription upstream; - TakeLastOneSubscriber(Subscriber<? super T> actual) { - super(actual); + TakeLastOneSubscriber(Subscriber<? super T> downstream) { + super(downstream); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -56,7 +56,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { value = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -65,14 +65,14 @@ public void onComplete() { if (v != null) { complete(v); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java index b139d31995..b48cdddc0b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java @@ -51,7 +51,7 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class TakeLastTimedSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -5677354903406201275L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long count; final long time; final TimeUnit unit; @@ -59,7 +59,7 @@ static final class TakeLastTimedSubscriber<T> extends AtomicInteger implements F final SpscLinkedArrayQueue<Object> queue; final boolean delayError; - Subscription s; + Subscription upstream; final AtomicLong requested = new AtomicLong(); @@ -69,7 +69,7 @@ static final class TakeLastTimedSubscriber<T> extends AtomicInteger implements F Throwable error; TakeLastTimedSubscriber(Subscriber<? super T> actual, long count, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.count = count; this.time = time; this.unit = unit; @@ -80,9 +80,9 @@ static final class TakeLastTimedSubscriber<T> extends AtomicInteger implements F @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -143,7 +143,7 @@ public void request(long n) { public void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); if (getAndIncrement() == 0) { queue.clear(); @@ -158,7 +158,7 @@ void drain() { int missed = 1; - final Subscriber<? super T> a = actual; + final Subscriber<? super T> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; final boolean delayError = this.delayError; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java index c1f52d22a8..a79501dccf 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java @@ -20,9 +20,9 @@ /** * Take with a generic Publisher source. - * + * <p>History: 2.0.7 - experimental * @param <T> the value type - * @since 2.0.7 - experimental + * @since 2.1 */ public final class FlowableTakePublisher<T> extends Flowable<T> { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java index c5352264ff..180a77ec68 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java @@ -42,54 +42,54 @@ static final class TakeUntilMainSubscriber<T> extends AtomicInteger implements F private static final long serialVersionUID = -4945480365982832967L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicLong requested; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; final AtomicThrowable error; final OtherSubscriber other; - TakeUntilMainSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + TakeUntilMainSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; this.requested = new AtomicLong(); - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.other = new OtherSubscriber(); this.error = new AtomicThrowable(); } @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } @Override public void onNext(T t) { - HalfSerializer.onNext(actual, t, this, error); + HalfSerializer.onNext(downstream, t, this, error); } @Override public void onError(Throwable t) { SubscriptionHelper.cancel(other); - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } @Override public void onComplete() { SubscriptionHelper.cancel(other); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } @Override public void request(long n) { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); SubscriptionHelper.cancel(other); } @@ -99,9 +99,7 @@ final class OtherSubscriber extends AtomicReference<Subscription> implements Flo @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -112,14 +110,14 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - SubscriptionHelper.cancel(s); - HalfSerializer.onError(actual, t, TakeUntilMainSubscriber.this, error); + SubscriptionHelper.cancel(upstream); + HalfSerializer.onError(downstream, t, TakeUntilMainSubscriber.this, error); } @Override public void onComplete() { - SubscriptionHelper.cancel(s); - HalfSerializer.onComplete(actual, TakeUntilMainSubscriber.this, error); + SubscriptionHelper.cancel(upstream); + HalfSerializer.onComplete(downstream, TakeUntilMainSubscriber.this, error); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java index 790800dc9d..cad253b254 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java @@ -34,40 +34,40 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class InnerSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; InnerSubscriber(Subscriber<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!done) { - actual.onNext(t); + downstream.onNext(t); boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } if (b) { done = true; - s.cancel(); - actual.onComplete(); + upstream.cancel(); + downstream.onComplete(); } } } @@ -76,7 +76,7 @@ public void onNext(T t) { public void onError(Throwable t) { if (!done) { done = true; - actual.onError(t); + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -86,18 +86,18 @@ public void onError(Throwable t) { public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java index 2190f0518c..d4436bd0a0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java @@ -34,23 +34,23 @@ protected void subscribeActual(Subscriber<? super T> s) { } static final class TakeWhileSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; TakeWhileSubscriber(Subscriber<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -64,19 +64,19 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.cancel(); + upstream.cancel(); onError(e); return; } if (!b) { done = true; - s.cancel(); - actual.onComplete(); + upstream.cancel(); + downstream.onComplete(); return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -86,7 +86,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -95,17 +95,17 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java index fd6b821609..ca10e8d47c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java @@ -52,12 +52,12 @@ static final class DebounceTimedSubscriber<T> implements FlowableSubscriber<T>, Subscription, Runnable { private static final long serialVersionUID = -9102637559663639004L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final long timeout; final TimeUnit unit; final Scheduler.Worker worker; - Subscription s; + Subscription upstream; final SequentialDisposable timer = new SequentialDisposable(); @@ -66,7 +66,7 @@ static final class DebounceTimedSubscriber<T> boolean done; DebounceTimedSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; @@ -74,9 +74,9 @@ static final class DebounceTimedSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -91,12 +91,12 @@ public void onNext(T t) { gate = true; long r = get(); if (r != 0L) { - actual.onNext(t); + downstream.onNext(t); BackpressureHelper.produced(this, 1); } else { done = true; cancel(); - actual.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); return; } @@ -107,8 +107,6 @@ public void onNext(T t) { timer.replace(worker.schedule(this, timeout, unit)); } - - } @Override @@ -123,7 +121,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); worker.dispose(); } @@ -133,7 +131,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); worker.dispose(); } @@ -146,7 +144,7 @@ public void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); worker.dispose(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java new file mode 100644 index 0000000000..b3fe22e129 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Emits the next or latest item when the given time elapses. + * <p> + * The operator emits the next item, then starts a timer. When the timer fires, + * it tries to emit the latest item from upstream. If there was no upstream item, + * in the meantime, the next upstream item is emitted immediately and the + * timed process repeats. + * <p>History: 2.1.14 - experimental + * @param <T> the upstream and downstream value type + * @since 2.2 + */ +public final class FlowableThrottleLatest<T> extends AbstractFlowableWithUpstream<T, T> { + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean emitLast; + + public FlowableThrottleLatest(Flowable<T> source, + long timeout, TimeUnit unit, Scheduler scheduler, + boolean emitLast) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new ThrottleLatestSubscriber<T>(s, timeout, unit, scheduler.createWorker(), emitLast)); + } + + static final class ThrottleLatestSubscriber<T> + extends AtomicInteger + implements FlowableSubscriber<T>, Subscription, Runnable { + + private static final long serialVersionUID = -8296689127439125014L; + + final Subscriber<? super T> downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final boolean emitLast; + + final AtomicReference<T> latest; + + final AtomicLong requested; + + Subscription upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + volatile boolean timerFired; + + long emitted; + + boolean timerRunning; + + ThrottleLatestSubscriber(Subscriber<? super T> downstream, + long timeout, TimeUnit unit, Scheduler.Worker worker, + boolean emitLast) { + this.downstream = downstream; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.emitLast = emitLast; + this.latest = new AtomicReference<T>(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + latest.set(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + worker.dispose(); + if (getAndIncrement() == 0) { + latest.lazySet(null); + } + } + + @Override + public void run() { + timerFired = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + AtomicReference<T> latest = this.latest; + AtomicLong requested = this.requested; + Subscriber<? super T> downstream = this.downstream; + + for (;;) { + + for (;;) { + if (cancelled) { + latest.lazySet(null); + return; + } + + boolean d = done; + + if (d && error != null) { + latest.lazySet(null); + downstream.onError(error); + worker.dispose(); + return; + } + + T v = latest.get(); + boolean empty = v == null; + + if (d) { + if (!empty && emitLast) { + v = latest.getAndSet(null); + long e = emitted; + if (e != requested.get()) { + emitted = e + 1; + downstream.onNext(v); + downstream.onComplete(); + } else { + downstream.onError(new MissingBackpressureException( + "Could not emit final value due to lack of requests")); + } + } else { + latest.lazySet(null); + downstream.onComplete(); + } + worker.dispose(); + return; + } + + if (empty) { + if (timerFired) { + timerRunning = false; + timerFired = false; + } + break; + } + + if (!timerRunning || timerFired) { + v = latest.getAndSet(null); + long e = emitted; + if (e != requested.get()) { + downstream.onNext(v); + emitted = e + 1; + } else { + upstream.cancel(); + downstream.onError(new MissingBackpressureException( + "Could not emit value due to lack of requests")); + worker.dispose(); + return; + } + + timerFired = false; + timerRunning = true; + worker.schedule(this, timeout, unit); + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java index e578736192..cf8b7087a7 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java @@ -31,33 +31,32 @@ public FlowableTimeInterval(Flowable<T> source, TimeUnit unit, Scheduler schedul this.unit = unit; } - @Override protected void subscribeActual(Subscriber<? super Timed<T>> s) { source.subscribe(new TimeIntervalSubscriber<T>(s, unit, scheduler)); } static final class TimeIntervalSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super Timed<T>> actual; + final Subscriber<? super Timed<T>> downstream; final TimeUnit unit; final Scheduler scheduler; - Subscription s; + Subscription upstream; long lastTime; TimeIntervalSubscriber(Subscriber<? super Timed<T>> actual, TimeUnit unit, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; this.unit = unit; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { lastTime = scheduler.now(unit); - this.s = s; - actual.onSubscribe(this); + this.upstream = s; + downstream.onSubscribe(this); } } @@ -67,27 +66,27 @@ public void onNext(T t) { long last = lastTime; lastTime = now; long delta = now - last; - actual.onNext(new Timed<T>(t, delta, unit)); + downstream.onNext(new Timed<T>(t, delta, unit)); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java index 7f9e7568eb..8363a5d0cb 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java @@ -14,7 +14,7 @@ package io.reactivex.internal.operators.flowable; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; import org.reactivestreams.*; @@ -22,12 +22,11 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; -import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.disposables.SequentialDisposable; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.subscribers.FullArbiterSubscriber; +import io.reactivex.internal.operators.flowable.FlowableTimeoutTimed.TimeoutSupport; import io.reactivex.internal.subscriptions.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subscribers.*; public final class FlowableTimeout<T, U, V> extends AbstractFlowableWithUpstream<T, T> { final Publisher<U> firstTimeoutIndicator; @@ -48,299 +47,343 @@ public FlowableTimeout( @Override protected void subscribeActual(Subscriber<? super T> s) { if (other == null) { - source.subscribe(new TimeoutSubscriber<T, U, V>( - new SerializedSubscriber<T>(s), - firstTimeoutIndicator, itemTimeoutIndicator)); + TimeoutSubscriber<T> parent = new TimeoutSubscriber<T>(s, itemTimeoutIndicator); + s.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); } else { - source.subscribe(new TimeoutOtherSubscriber<T, U, V>( - s, firstTimeoutIndicator, itemTimeoutIndicator, other)); + TimeoutFallbackSubscriber<T> parent = new TimeoutFallbackSubscriber<T>(s, itemTimeoutIndicator, other); + s.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); } } - static final class TimeoutSubscriber<T, U, V> implements FlowableSubscriber<T>, Subscription, OnTimeout { - final Subscriber<? super T> actual; - final Publisher<U> firstTimeoutIndicator; - final Function<? super T, ? extends Publisher<V>> itemTimeoutIndicator; + interface TimeoutSelectorSupport extends TimeoutSupport { + void onTimeoutError(long idx, Throwable ex); + } + + static final class TimeoutSubscriber<T> extends AtomicLong + implements FlowableSubscriber<T>, Subscription, TimeoutSelectorSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber<? super T> downstream; - Subscription s; + final Function<? super T, ? extends Publisher<?>> itemTimeoutIndicator; - volatile boolean cancelled; + final SequentialDisposable task; - volatile long index; + final AtomicReference<Subscription> upstream; - final AtomicReference<Disposable> timeout = new AtomicReference<Disposable>(); + final AtomicLong requested; - TimeoutSubscriber(Subscriber<? super T> actual, - Publisher<U> firstTimeoutIndicator, - Function<? super T, ? extends Publisher<V>> itemTimeoutIndicator) { - this.actual = actual; - this.firstTimeoutIndicator = firstTimeoutIndicator; + TimeoutSubscriber(Subscriber<? super T> actual, Function<? super T, ? extends Publisher<?>> itemTimeoutIndicator) { + this.downstream = actual; this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Subscription>(); + this.requested = new AtomicLong(); } @Override public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { - return; - } - this.s = s; - - if (cancelled) { - return; - } - - Subscriber<? super T> a = actual; - - Publisher<U> p = firstTimeoutIndicator; - - if (p != null) { - TimeoutInnerSubscriber<T, U, V> tis = new TimeoutInnerSubscriber<T, U, V>(this, 0); - - if (timeout.compareAndSet(null, tis)) { - a.onSubscribe(this); - p.subscribe(tis); - } - } else { - a.onSubscribe(this); - } + SubscriptionHelper.deferredSetOnce(upstream, requested, s); } @Override public void onNext(T t) { - long idx = index + 1; - index = idx; - - actual.onNext(t); + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } - Disposable d = timeout.get(); + Disposable d = task.get(); if (d != null) { d.dispose(); } - Publisher<V> p; + downstream.onNext(t); + + Publisher<?> itemTimeoutPublisher; try { - p = ObjectHelper.requireNonNull(itemTimeoutIndicator.apply(t), "The publisher returned is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - cancel(); - actual.onError(e); + itemTimeoutPublisher = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null Publisher."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + getAndSet(Long.MAX_VALUE); + downstream.onError(ex); return; } - TimeoutInnerSubscriber<T, U, V> tis = new TimeoutInnerSubscriber<T, U, V>(this, idx); + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutPublisher.subscribe(consumer); + } + } - if (timeout.compareAndSet(d, tis)) { - p.subscribe(tis); + void startFirstTimeout(Publisher<?> firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } } } @Override public void onError(Throwable t) { - cancel(); - actual.onError(t); + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } } @Override public void onComplete() { - cancel(); - actual.onComplete(); + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + } } @Override - public void request(long n) { - s.request(n); + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(new TimeoutException()); + } } @Override - public void cancel() { - cancelled = true; - s.cancel(); - DisposableHelper.dispose(timeout); + public void onTimeoutError(long idx, Throwable ex) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } } @Override - public void timeout(long idx) { - if (idx == index) { - cancel(); - actual.onError(new TimeoutException()); - } + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + task.dispose(); } } - interface OnTimeout { - void timeout(long index); + static final class TimeoutFallbackSubscriber<T> extends SubscriptionArbiter + implements FlowableSubscriber<T>, TimeoutSelectorSupport { - void onError(Throwable e); - } + private static final long serialVersionUID = 3764492702657003550L; - static final class TimeoutInnerSubscriber<T, U, V> extends DisposableSubscriber<Object> { - final OnTimeout parent; - final long index; + final Subscriber<? super T> downstream; - boolean done; + final Function<? super T, ? extends Publisher<?>> itemTimeoutIndicator; - TimeoutInnerSubscriber(OnTimeout parent, final long index) { - this.parent = parent; - this.index = index; - } + final SequentialDisposable task; - @Override - public void onNext(Object t) { - if (done) { - return; - } - done = true; - cancel(); - parent.timeout(index); + final AtomicReference<Subscription> upstream; + + final AtomicLong index; + + Publisher<? extends T> fallback; + + long consumed; + + TimeoutFallbackSubscriber(Subscriber<? super T> actual, + Function<? super T, ? extends Publisher<?>> itemTimeoutIndicator, + Publisher<? extends T> fallback) { + super(true); + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Subscription>(); + this.fallback = fallback; + this.index = new AtomicLong(); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + setSubscription(s); } - done = true; - parent.onError(t); } @Override - public void onComplete() { - if (done) { + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { return; } - done = true; - parent.timeout(index); - } - } - static final class TimeoutOtherSubscriber<T, U, V> implements FlowableSubscriber<T>, Disposable, OnTimeout { - final Subscriber<? super T> actual; - final Publisher<U> firstTimeoutIndicator; - final Function<? super T, ? extends Publisher<V>> itemTimeoutIndicator; - final Publisher<? extends T> other; - final FullArbiter<T> arbiter; + Disposable d = task.get(); + if (d != null) { + d.dispose(); + } - Subscription s; + consumed++; - boolean done; + downstream.onNext(t); - volatile boolean cancelled; + Publisher<?> itemTimeoutPublisher; - volatile long index; + try { + itemTimeoutPublisher = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null Publisher."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + index.getAndSet(Long.MAX_VALUE); + downstream.onError(ex); + return; + } - final AtomicReference<Disposable> timeout = new AtomicReference<Disposable>(); + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutPublisher.subscribe(consumer); + } + } - TimeoutOtherSubscriber(Subscriber<? super T> actual, - Publisher<U> firstTimeoutIndicator, - Function<? super T, ? extends Publisher<V>> itemTimeoutIndicator, Publisher<? extends T> other) { - this.actual = actual; - this.firstTimeoutIndicator = firstTimeoutIndicator; - this.itemTimeoutIndicator = itemTimeoutIndicator; - this.other = other; - this.arbiter = new FullArbiter<T>(actual, this, 8); + void startFirstTimeout(Publisher<?> firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } + } } @Override - public void onSubscribe(Subscription s) { - if (!SubscriptionHelper.validate(this.s, s)) { - return; + public void onError(Throwable t) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + task.dispose(); + } else { + RxJavaPlugins.onError(t); } - this.s = s; + } - if (!arbiter.setSubscription(s)) { - return; + @Override + public void onComplete() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + task.dispose(); } - Subscriber<? super T> a = actual; + } - Publisher<U> p = firstTimeoutIndicator; + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); - if (p != null) { - TimeoutInnerSubscriber<T, U, V> tis = new TimeoutInnerSubscriber<T, U, V>(this, 0); + Publisher<? extends T> f = fallback; + fallback = null; - if (timeout.compareAndSet(null, tis)) { - a.onSubscribe(arbiter); - p.subscribe(tis); + long c = consumed; + if (c != 0L) { + produced(c); } - } else { - a.onSubscribe(arbiter); + + f.subscribe(new FlowableTimeoutTimed.FallbackSubscriber<T>(downstream, this)); } } @Override - public void onNext(T t) { - if (done) { - return; - } - long idx = index + 1; - index = idx; + public void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); - if (!arbiter.onNext(t, s)) { - return; + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); } + } - Disposable d = timeout.get(); - if (d != null) { - d.dispose(); - } + @Override + public void cancel() { + super.cancel(); + task.dispose(); + } + } - Publisher<V> p; + static final class TimeoutConsumer extends AtomicReference<Subscription> + implements FlowableSubscriber<Object>, Disposable { - try { - p = ObjectHelper.requireNonNull(itemTimeoutIndicator.apply(t), "The publisher returned is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - actual.onError(e); - return; - } + private static final long serialVersionUID = 8708641127342403073L; + + final TimeoutSelectorSupport parent; - TimeoutInnerSubscriber<T, U, V> tis = new TimeoutInnerSubscriber<T, U, V>(this, idx); + final long idx; - if (timeout.compareAndSet(d, tis)) { - p.subscribe(tis); + TimeoutConsumer(long idx, TimeoutSelectorSupport parent) { + this.idx = idx; + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + Subscription upstream = get(); + if (upstream != SubscriptionHelper.CANCELLED) { + upstream.cancel(); + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeout(idx); } } @Override public void onError(Throwable t) { - if (done) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeoutError(idx, t); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - dispose(); - arbiter.onError(t, s); } @Override public void onComplete() { - if (done) { - return; + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeout(idx); } - done = true; - dispose(); - arbiter.onComplete(s); } @Override public void dispose() { - cancelled = true; - s.cancel(); - DisposableHelper.dispose(timeout); + SubscriptionHelper.cancel(this); } @Override public boolean isDisposed() { - return cancelled; - } - - @Override - public void timeout(long idx) { - if (idx == index) { - dispose(); - other.subscribe(new FullArbiterSubscriber<T>(arbiter)); - } + return this.get() == SubscriptionHelper.CANCELLED; } } + } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java index 103f40ca60..d25acdc3e3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java @@ -14,18 +14,16 @@ package io.reactivex.internal.operators.flowable; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Scheduler.Worker; -import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.DisposableHelper; -import io.reactivex.internal.subscribers.FullArbiterSubscriber; +import io.reactivex.internal.disposables.SequentialDisposable; import io.reactivex.internal.subscriptions.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subscribers.SerializedSubscriber; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; public final class FlowableTimeoutTimed<T> extends AbstractFlowableWithUpstream<T, T> { final long timeout; @@ -33,8 +31,6 @@ public final class FlowableTimeoutTimed<T> extends AbstractFlowableWithUpstream< final Scheduler scheduler; final Publisher<? extends T> other; - static final Disposable NEW_TIMER = new EmptyDispose(); - public FlowableTimeoutTimed(Flowable<T> source, long timeout, TimeUnit unit, Scheduler scheduler, Publisher<? extends T> other) { super(source); @@ -47,256 +43,282 @@ public FlowableTimeoutTimed(Flowable<T> source, @Override protected void subscribeActual(Subscriber<? super T> s) { if (other == null) { - source.subscribe(new TimeoutTimedSubscriber<T>( - new SerializedSubscriber<T>(s), // because errors can race - timeout, unit, scheduler.createWorker())); + TimeoutSubscriber<T> parent = new TimeoutSubscriber<T>(s, timeout, unit, scheduler.createWorker()); + s.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); } else { - source.subscribe(new TimeoutTimedOtherSubscriber<T>( - s, // the FullArbiter serializes - timeout, unit, scheduler.createWorker(), other)); + TimeoutFallbackSubscriber<T> parent = new TimeoutFallbackSubscriber<T>(s, timeout, unit, scheduler.createWorker(), other); + s.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); } } - static final class TimeoutTimedOtherSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final Subscriber<? super T> actual; + static final class TimeoutSubscriber<T> extends AtomicLong + implements FlowableSubscriber<T>, Subscription, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber<? super T> downstream; + final long timeout; - final TimeUnit unit; - final Scheduler.Worker worker; - final Publisher<? extends T> other; - Subscription s; + final TimeUnit unit; - final FullArbiter<T> arbiter; + final Scheduler.Worker worker; - Disposable timer; + final SequentialDisposable task; - volatile long index; + final AtomicReference<Subscription> upstream; - volatile boolean done; + final AtomicLong requested; - TimeoutTimedOtherSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, Worker worker, - Publisher<? extends T> other) { - this.actual = actual; + TimeoutSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, Scheduler.Worker worker) { + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; - this.other = other; - this.arbiter = new FullArbiter<T>(actual, this, 8); + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Subscription>(); + this.requested = new AtomicLong(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - if (arbiter.setSubscription(s)) { - actual.onSubscribe(arbiter); - - scheduleTimeout(0L); - } - } + SubscriptionHelper.deferredSetOnce(upstream, requested, s); } @Override public void onNext(T t) { - if (done) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { return; } - long idx = index + 1; - index = idx; - if (arbiter.onNext(t, s)) { - scheduleTimeout(idx); - } - } + task.get().dispose(); - void scheduleTimeout(final long idx) { - if (timer != null) { - timer.dispose(); - } + downstream.onNext(t); - timer = worker.schedule(new TimeoutTask(idx), timeout, unit); + startTimeout(idx + 1); } - void subscribeNext() { - other.subscribe(new FullArbiterSubscriber<T>(arbiter)); + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); } @Override public void onError(Throwable t) { - if (done) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - arbiter.onError(t, s); - worker.dispose(); } @Override public void onComplete() { - if (done) { - return; + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); } - done = true; - arbiter.onComplete(s); - worker.dispose(); } @Override - public void dispose() { - s.cancel(); - worker.dispose(); + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + + worker.dispose(); + } } @Override - public boolean isDisposed() { - return worker.isDisposed(); + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + worker.dispose(); } + } - final class TimeoutTask implements Runnable { - private final long idx; + static final class TimeoutTask implements Runnable { - TimeoutTask(long idx) { - this.idx = idx; - } + final TimeoutSupport parent; - @Override - public void run() { - if (idx == index) { - done = true; - s.cancel(); - worker.dispose(); + final long idx; - subscribeNext(); + TimeoutTask(long idx, TimeoutSupport parent) { + this.idx = idx; + this.parent = parent; + } - } - } + @Override + public void run() { + parent.onTimeout(idx); } } - static final class TimeoutTimedSubscriber<T> implements FlowableSubscriber<T>, Disposable, Subscription { - final Subscriber<? super T> actual; + static final class TimeoutFallbackSubscriber<T> extends SubscriptionArbiter + implements FlowableSubscriber<T>, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber<? super T> downstream; + final long timeout; + final TimeUnit unit; + final Scheduler.Worker worker; - Subscription s; + final SequentialDisposable task; - Disposable timer; + final AtomicReference<Subscription> upstream; - volatile long index; + final AtomicLong index; - volatile boolean done; + long consumed; - TimeoutTimedSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + Publisher<? extends T> fallback; + + TimeoutFallbackSubscriber(Subscriber<? super T> actual, long timeout, TimeUnit unit, + Scheduler.Worker worker, Publisher<? extends T> fallback) { + super(true); + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; + this.fallback = fallback; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Subscription>(); + this.index = new AtomicLong(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - scheduleTimeout(0L); + if (SubscriptionHelper.setOnce(upstream, s)) { + setSubscription(s); } } @Override public void onNext(T t) { - if (done) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { return; } - long idx = index + 1; - index = idx; - actual.onNext(t); + task.get().dispose(); - scheduleTimeout(idx); - } + consumed++; - void scheduleTimeout(final long idx) { - if (timer != null) { - timer.dispose(); - } + downstream.onNext(t); - timer = worker.schedule(new TimeoutTask(idx), timeout, unit); + startTimeout(idx + 1); + } + + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); } @Override public void onError(Throwable t) { - if (done) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - - actual.onError(t); - worker.dispose(); } @Override public void onComplete() { - if (done) { - return; - } - done = true; + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); - actual.onComplete(); - worker.dispose(); - } + downstream.onComplete(); - @Override - public void dispose() { - s.cancel(); - worker.dispose(); + worker.dispose(); + } } @Override - public boolean isDisposed() { - return worker.isDisposed(); - } + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); - @Override - public void request(long n) { - s.request(n); + long c = consumed; + if (c != 0L) { + produced(c); + } + + Publisher<? extends T> f = fallback; + fallback = null; + + f.subscribe(new FallbackSubscriber<T>(downstream, this)); + + worker.dispose(); + } } @Override public void cancel() { - dispose(); + super.cancel(); + worker.dispose(); } + } - final class TimeoutTask implements Runnable { - private final long idx; + static final class FallbackSubscriber<T> implements FlowableSubscriber<T> { - TimeoutTask(long idx) { - this.idx = idx; - } + final Subscriber<? super T> downstream; - @Override - public void run() { - if (idx == index) { - done = true; - dispose(); + final SubscriptionArbiter arbiter; - actual.onError(new TimeoutException()); - } - } + FallbackSubscriber(Subscriber<? super T> actual, SubscriptionArbiter arbiter) { + this.downstream = actual; + this.arbiter = arbiter; + } + + @Override + public void onSubscribe(Subscription s) { + arbiter.setSubscription(s); } - } - static final class EmptyDispose implements Disposable { @Override - public void dispose() { } + public void onNext(T t) { + downstream.onNext(t); + } @Override - public boolean isDisposed() { - return true; + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); } } + interface TimeoutSupport { + void onTimeout(long idx); + + } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java index b7241ea4ac..7c829267cf 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java @@ -49,12 +49,12 @@ static final class TimerSubscriber extends AtomicReference<Disposable> private static final long serialVersionUID = -2809475196591179431L; - final Subscriber<? super Long> actual; + final Subscriber<? super Long> downstream; volatile boolean requested; - TimerSubscriber(Subscriber<? super Long> actual) { - this.actual = actual; + TimerSubscriber(Subscriber<? super Long> downstream) { + this.downstream = downstream; } @Override @@ -73,12 +73,12 @@ public void cancel() { public void run() { if (get() != DisposableHelper.DISPOSED) { if (requested) { - actual.onNext(0L); + downstream.onNext(0L); lazySet(EmptyDisposable.INSTANCE); - actual.onComplete(); + downstream.onComplete(); } else { lazySet(EmptyDisposable.INSTANCE); - actual.onError(new MissingBackpressureException("Can't deliver value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Can't deliver value due to lack of requests")); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java index 75ef64ce2f..60508e3e7e 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java @@ -44,14 +44,12 @@ protected void subscribeActual(Subscriber<? super U> s) { source.subscribe(new ToListSubscriber<T, U>(s, coll)); } - static final class ToListSubscriber<T, U extends Collection<? super T>> extends DeferredScalarSubscription<U> implements FlowableSubscriber<T>, Subscription { - private static final long serialVersionUID = -8134157938864266736L; - Subscription s; + Subscription upstream; ToListSubscriber(Subscriber<? super U> actual, U collection) { super(actual); @@ -60,9 +58,9 @@ static final class ToListSubscriber<T, U extends Collection<? super T>> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -78,7 +76,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { value = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -89,7 +87,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java index 365e9762cd..e4a8abd565 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java @@ -45,16 +45,16 @@ public FlowableToListSingle(Flowable<T> source, Callable<U> collectionSupplier) } @Override - protected void subscribeActual(SingleObserver<? super U> s) { + protected void subscribeActual(SingleObserver<? super U> observer) { U coll; try { coll = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - source.subscribe(new ToListSubscriber<T, U>(s, coll)); + source.subscribe(new ToListSubscriber<T, U>(observer, coll)); } @Override @@ -65,22 +65,22 @@ public Flowable<U> fuseToFlowable() { static final class ToListSubscriber<T, U extends Collection<? super T>> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super U> actual; + final SingleObserver<? super U> downstream; - Subscription s; + Subscription upstream; U value; ToListSubscriber(SingleObserver<? super U> actual, U collection) { - this.actual = actual; + this.downstream = actual; this.value = collection; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @@ -93,25 +93,25 @@ public void onNext(T t) { @Override public void onError(Throwable t) { value = null; - s = SubscriptionHelper.CANCELLED; - actual.onError(t); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); } @Override public void onComplete() { - s = SubscriptionHelper.CANCELLED; - actual.onSuccess(value); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(value); } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java index b5d6a1d001..0790d4b444 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java @@ -37,28 +37,28 @@ static final class UnsubscribeSubscriber<T> extends AtomicBoolean implements Flo private static final long serialVersionUID = 1015244841293359600L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Scheduler scheduler; - Subscription s; + Subscription upstream; UnsubscribeSubscriber(Subscriber<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!get()) { - actual.onNext(t); + downstream.onNext(t); } } @@ -68,19 +68,19 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!get()) { - actual.onComplete(); + downstream.onComplete(); } } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override @@ -93,7 +93,7 @@ public void cancel() { final class Cancellation implements Runnable { @Override public void run() { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java index 9cc64d860f..2d3a83fc4a 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java @@ -78,15 +78,15 @@ static final class UsingSubscriber<T, D> extends AtomicBoolean implements Flowab private static final long serialVersionUID = 5904473792286235046L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final D resource; final Consumer<? super D> disposer; final boolean eager; - Subscription s; + Subscription upstream; UsingSubscriber(Subscriber<? super T> actual, D resource, Consumer<? super D> disposer, boolean eager) { - this.actual = actual; + this.downstream = actual; this.resource = resource; this.disposer = disposer; this.eager = eager; @@ -94,15 +94,15 @@ static final class UsingSubscriber<T, D> extends AtomicBoolean implements Flowab @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override @@ -118,15 +118,15 @@ public void onError(Throwable t) { } } - s.cancel(); + upstream.cancel(); if (innerError != null) { - actual.onError(new CompositeException(t, innerError)); + downstream.onError(new CompositeException(t, innerError)); } else { - actual.onError(t); + downstream.onError(t); } } else { - actual.onError(t); - s.cancel(); + downstream.onError(t); + upstream.cancel(); disposeAfter(); } } @@ -139,29 +139,29 @@ public void onComplete() { disposer.accept(resource); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } } - s.cancel(); - actual.onComplete(); + upstream.cancel(); + downstream.onComplete(); } else { - actual.onComplete(); - s.cancel(); + downstream.onComplete(); + upstream.cancel(); disposeAfter(); } } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { disposeAfter(); - s.cancel(); + upstream.cancel(); } void disposeAfter() { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java index f678e73ee2..e9c1259b65 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java @@ -55,10 +55,9 @@ static final class WindowExactSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Subscription, Runnable { - private static final long serialVersionUID = -2365647875069161133L; - final Subscriber<? super Flowable<T>> actual; + final Subscriber<? super Flowable<T>> downstream; final long size; @@ -68,13 +67,13 @@ static final class WindowExactSubscriber<T> long index; - Subscription s; + Subscription upstream; UnicastProcessor<T> window; WindowExactSubscriber(Subscriber<? super Flowable<T>> actual, long size, int bufferSize) { super(1); - this.actual = actual; + this.downstream = actual; this.size = size; this.once = new AtomicBoolean(); this.bufferSize = bufferSize; @@ -82,9 +81,9 @@ static final class WindowExactSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -99,7 +98,7 @@ public void onNext(T t) { w = UnicastProcessor.<T>create(bufferSize, this); window = w; - actual.onNext(w); + downstream.onNext(w); } i++; @@ -123,7 +122,7 @@ public void onError(Throwable t) { w.onError(t); } - actual.onError(t); + downstream.onError(t); } @Override @@ -134,14 +133,14 @@ public void onComplete() { w.onComplete(); } - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { long u = BackpressureHelper.multiplyCap(size, n); - s.request(u); + upstream.request(u); } } @@ -155,7 +154,7 @@ public void cancel() { @Override public void run() { if (decrementAndGet() == 0) { - s.cancel(); + upstream.cancel(); } } } @@ -164,10 +163,9 @@ static final class WindowSkipSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Subscription, Runnable { - private static final long serialVersionUID = -8792836352386833856L; - final Subscriber<? super Flowable<T>> actual; + final Subscriber<? super Flowable<T>> downstream; final long size; @@ -181,13 +179,13 @@ static final class WindowSkipSubscriber<T> long index; - Subscription s; + Subscription upstream; UnicastProcessor<T> window; WindowSkipSubscriber(Subscriber<? super Flowable<T>> actual, long size, long skip, int bufferSize) { super(1); - this.actual = actual; + this.downstream = actual; this.size = size; this.skip = skip; this.once = new AtomicBoolean(); @@ -197,9 +195,9 @@ static final class WindowSkipSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -211,11 +209,10 @@ public void onNext(T t) { if (i == 0) { getAndIncrement(); - w = UnicastProcessor.<T>create(bufferSize, this); window = w; - actual.onNext(w); + downstream.onNext(w); } i++; @@ -244,7 +241,7 @@ public void onError(Throwable t) { w.onError(t); } - actual.onError(t); + downstream.onError(t); } @Override @@ -255,7 +252,7 @@ public void onComplete() { w.onComplete(); } - actual.onComplete(); + downstream.onComplete(); } @Override @@ -265,10 +262,10 @@ public void request(long n) { long u = BackpressureHelper.multiplyCap(size, n); long v = BackpressureHelper.multiplyCap(skip - size, n - 1); long w = BackpressureHelper.addCap(u, v); - s.request(w); + upstream.request(w); } else { long u = BackpressureHelper.multiplyCap(skip, n); - s.request(u); + upstream.request(u); } } } @@ -283,7 +280,7 @@ public void cancel() { @Override public void run() { if (decrementAndGet() == 0) { - s.cancel(); + upstream.cancel(); } } } @@ -292,10 +289,9 @@ static final class WindowOverlapSubscriber<T> extends AtomicInteger implements FlowableSubscriber<T>, Subscription, Runnable { - private static final long serialVersionUID = 2428527070996323976L; - final Subscriber<? super Flowable<T>> actual; + final Subscriber<? super Flowable<T>> downstream; final SpscLinkedArrayQueue<UnicastProcessor<T>> queue; @@ -319,7 +315,7 @@ static final class WindowOverlapSubscriber<T> long produced; - Subscription s; + Subscription upstream; volatile boolean done; Throwable error; @@ -328,7 +324,7 @@ static final class WindowOverlapSubscriber<T> WindowOverlapSubscriber(Subscriber<? super Flowable<T>> actual, long size, long skip, int bufferSize) { super(1); - this.actual = actual; + this.downstream = actual; this.size = size; this.skip = skip; this.queue = new SpscLinkedArrayQueue<UnicastProcessor<T>>(bufferSize); @@ -342,9 +338,9 @@ static final class WindowOverlapSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -431,7 +427,7 @@ void drain() { return; } - final Subscriber<? super Flowable<T>> a = actual; + final Subscriber<? super Flowable<T>> a = downstream; final SpscLinkedArrayQueue<UnicastProcessor<T>> q = queue; int missed = 1; @@ -508,10 +504,10 @@ public void request(long n) { if (!firstRequest.get() && firstRequest.compareAndSet(false, true)) { long u = BackpressureHelper.multiplyCap(skip, n - 1); long v = BackpressureHelper.addCap(size, u); - s.request(v); + upstream.request(v); } else { long u = BackpressureHelper.multiplyCap(skip, n); - s.request(u); + upstream.request(u); } drain(); @@ -529,7 +525,7 @@ public void cancel() { @Override public void run() { if (decrementAndGet() == 0) { - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java index 9cf1b1100c..a398eb040f 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java @@ -17,185 +17,208 @@ import org.reactivestreams.*; -import io.reactivex.Flowable; -import io.reactivex.disposables.Disposable; +import io.reactivex.*; import io.reactivex.exceptions.MissingBackpressureException; -import io.reactivex.internal.disposables.DisposableHelper; -import io.reactivex.internal.fuseable.SimplePlainQueue; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.subscribers.QueueDrainSubscriber; import io.reactivex.internal.subscriptions.SubscriptionHelper; -import io.reactivex.internal.util.NotificationLite; +import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.UnicastProcessor; -import io.reactivex.subscribers.*; +import io.reactivex.subscribers.DisposableSubscriber; public final class FlowableWindowBoundary<T, B> extends AbstractFlowableWithUpstream<T, Flowable<T>> { final Publisher<B> other; - final int bufferSize; + final int capacityHint; - public FlowableWindowBoundary(Flowable<T> source, Publisher<B> other, int bufferSize) { + public FlowableWindowBoundary(Flowable<T> source, Publisher<B> other, int capacityHint) { super(source); this.other = other; - this.bufferSize = bufferSize; + this.capacityHint = capacityHint; } @Override - protected void subscribeActual(Subscriber<? super Flowable<T>> s) { - source.subscribe( - new WindowBoundaryMainSubscriber<T, B>( - new SerializedSubscriber<Flowable<T>>(s), other, bufferSize)); + protected void subscribeActual(Subscriber<? super Flowable<T>> subscriber) { + WindowBoundaryMainSubscriber<T, B> parent = new WindowBoundaryMainSubscriber<T, B>(subscriber, capacityHint); + + subscriber.onSubscribe(parent); + + parent.innerNext(); + + other.subscribe(parent.boundarySubscriber); + + source.subscribe(parent); } static final class WindowBoundaryMainSubscriber<T, B> - extends QueueDrainSubscriber<T, Object, Flowable<T>> - implements Subscription { + extends AtomicInteger + implements FlowableSubscriber<T>, Subscription, Runnable { - final Publisher<B> other; - final int bufferSize; + private static final long serialVersionUID = 2233020065421370272L; - Subscription s; + final Subscriber<? super Flowable<T>> downstream; - final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); + final int capacityHint; - UnicastProcessor<T> window; + final WindowBoundaryInnerSubscriber<T, B> boundarySubscriber; - static final Object NEXT = new Object(); + final AtomicReference<Subscription> upstream; - final AtomicLong windows = new AtomicLong(); + final AtomicInteger windows; - WindowBoundaryMainSubscriber(Subscriber<? super Flowable<T>> actual, Publisher<B> other, - int bufferSize) { - super(actual, new MpscLinkedQueue<Object>()); - this.other = other; - this.bufferSize = bufferSize; - windows.lazySet(1); - } + final MpscLinkedQueue<Object> queue; - @Override - public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + final AtomicThrowable errors; - Subscriber<? super Flowable<T>> a = actual; - a.onSubscribe(this); + final AtomicBoolean stopWindows; - if (cancelled) { - return; - } + final AtomicLong requested; - UnicastProcessor<T> w = UnicastProcessor.<T>create(bufferSize); + static final Object NEXT_WINDOW = new Object(); - long r = requested(); - if (r != 0L) { - a.onNext(w); - if (r != Long.MAX_VALUE) { - produced(1); - } - } else { - a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests")); - return; - } + volatile boolean done; - window = w; + UnicastProcessor<T> window; - WindowBoundaryInnerSubscriber<T, B> inner = new WindowBoundaryInnerSubscriber<T, B>(this); + long emitted; + + WindowBoundaryMainSubscriber(Subscriber<? super Flowable<T>> downstream, int capacityHint) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundarySubscriber = new WindowBoundaryInnerSubscriber<T, B>(this); + this.upstream = new AtomicReference<Subscription>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue<Object>(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.requested = new AtomicLong(); + } - if (boundary.compareAndSet(null, inner)) { - windows.getAndIncrement(); - s.request(Long.MAX_VALUE); - other.subscribe(inner); - } - } + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(upstream, s, Long.MAX_VALUE); } @Override public void onNext(T t) { - if (fastEnter()) { - UnicastProcessor<T> w = window; - - w.onNext(t); + queue.offer(t); + drain(); + } - if (leave(-1) == 0) { - return; - } + @Override + public void onError(Throwable e) { + boundarySubscriber.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); } else { - queue.offer(NotificationLite.next(t)); - if (!enter()) { - return; - } + RxJavaPlugins.onError(e); } - drainLoop(); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - error = t; + public void onComplete() { + boundarySubscriber.dispose(); done = true; - if (enter()) { - drainLoop(); - } + drain(); + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + @Override + public void cancel() { + if (stopWindows.compareAndSet(false, true)) { + boundarySubscriber.dispose(); + if (windows.decrementAndGet() == 0) { + SubscriptionHelper.cancel(upstream); + } } - - actual.onError(t); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - if (enter()) { - drainLoop(); - } + public void request(long n) { + BackpressureHelper.add(requested, n); + } + @Override + public void run() { if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + SubscriptionHelper.cancel(upstream); } + } - actual.onComplete(); - + void innerNext() { + queue.offer(NEXT_WINDOW); + drain(); } - @Override - public void request(long n) { - requested(n); + void innerError(Throwable e) { + SubscriptionHelper.cancel(upstream); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } } - @Override - public void cancel() { - cancelled = true; + void innerComplete() { + SubscriptionHelper.cancel(upstream); + done = true; + drain(); } - void drainLoop() { - final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; - UnicastProcessor<T> w = window; + Subscriber<? super Flowable<T>> downstream = this.downstream; + MpscLinkedQueue<Object> queue = this.queue; + AtomicThrowable errors = this.errors; + long emitted = this.emitted; + for (;;) { for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastProcessor<T> w = window; + boolean d = done; - Object o = q.poll(); + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); - boolean empty = o == null; + boolean empty = v == null; if (d && empty) { - DisposableHelper.dispose(boundary); - Throwable e = error; - if (e != null) { - w.onError(e); + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); } else { - w.onComplete(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); } return; } @@ -204,64 +227,44 @@ void drainLoop() { break; } - if (o == NEXT) { - w.onComplete(); - - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); - return; - } - - if (cancelled) { - continue; - } + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } - w = UnicastProcessor.<T>create(bufferSize); + if (w != null) { + window = null; + w.onComplete(); + } - long r = requested(); - if (r != 0L) { - windows.getAndIncrement(); + if (!stopWindows.get()) { + w = UnicastProcessor.create(capacityHint, this); + window = w; + windows.getAndIncrement(); - a.onNext(w); - if (r != Long.MAX_VALUE) { - produced(1); - } + if (emitted != requested.get()) { + emitted++; + downstream.onNext(w); } else { - // don't emit new windows - cancelled = true; - a.onError(new MissingBackpressureException("Could not deliver new window due to lack of requests")); - continue; + SubscriptionHelper.cancel(upstream); + boundarySubscriber.dispose(); + errors.addThrowable(new MissingBackpressureException("Could not deliver a window due to lack of requests")); + done = true; } - - window = w; - continue; } - - w.onNext(NotificationLite.<T>getValue(o)); } - missed = leave(-missed); + this.emitted = emitted; + missed = addAndGet(-missed); if (missed == 0) { - return; + break; } } } - - void next() { - queue.offer(NEXT); - if (enter()) { - drainLoop(); - } - } - - @Override - public boolean accept(Subscriber<? super Flowable<T>> a, Object v) { - // not used by this operator - return false; - } } static final class WindowBoundaryInnerSubscriber<T, B> extends DisposableSubscriber<B> { + final WindowBoundaryMainSubscriber<T, B> parent; boolean done; @@ -275,7 +278,7 @@ public void onNext(B t) { if (done) { return; } - parent.next(); + parent.innerNext(); } @Override @@ -285,7 +288,7 @@ public void onError(Throwable t) { return; } done = true; - parent.onError(t); + parent.innerError(t); } @Override @@ -294,7 +297,7 @@ public void onComplete() { return; } done = true; - parent.onComplete(); + parent.innerComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java index 50c7e45dfb..d9d6ffa517 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java @@ -63,7 +63,7 @@ static final class WindowBoundaryMainSubscriber<T, B, V> final int bufferSize; final CompositeDisposable resources; - Subscription s; + Subscription upstream; final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); @@ -71,6 +71,8 @@ static final class WindowBoundaryMainSubscriber<T, B, V> final AtomicLong windows = new AtomicLong(); + final AtomicBoolean stopWindows = new AtomicBoolean(); + WindowBoundaryMainSubscriber(Subscriber<? super Flowable<T>> actual, Publisher<B> open, Function<? super B, ? extends Publisher<V>> close, int bufferSize) { super(actual, new MpscLinkedQueue<Object>()); @@ -84,19 +86,18 @@ static final class WindowBoundaryMainSubscriber<T, B, V> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); - if (cancelled) { + if (stopWindows.get()) { return; } OperatorWindowBoundaryOpenSubscriber<T, B> os = new OperatorWindowBoundaryOpenSubscriber<T, B>(this); if (boundary.compareAndSet(null, os)) { - windows.getAndIncrement(); s.request(Long.MAX_VALUE); open.subscribe(os); } @@ -141,7 +142,7 @@ public void onError(Throwable t) { resources.dispose(); } - actual.onError(t); + downstream.onError(t); } @Override @@ -159,15 +160,15 @@ public void onComplete() { resources.dispose(); } - actual.onComplete(); + downstream.onComplete(); } void error(Throwable t) { - s.cancel(); + upstream.cancel(); resources.dispose(); DisposableHelper.dispose(boundary); - actual.onError(t); + downstream.onError(t); } @Override @@ -177,7 +178,12 @@ public void request(long n) { @Override public void cancel() { - cancelled = true; + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } + } } void dispose() { @@ -187,7 +193,7 @@ void dispose() { void drainLoop() { final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + final Subscriber<? super Flowable<T>> a = downstream; final List<UnicastProcessor<T>> ws = this.ws; int missed = 1; @@ -236,11 +242,10 @@ void drainLoop() { continue; } - if (cancelled) { + if (stopWindows.get()) { continue; } - w = UnicastProcessor.<T>create(bufferSize); long r = requested(); @@ -251,7 +256,7 @@ void drainLoop() { produced(1); } } else { - cancelled = true; + cancel(); a.onError(new MissingBackpressureException("Could not deliver new window due to lack of requests")); continue; } @@ -261,7 +266,7 @@ void drainLoop() { try { p = ObjectHelper.requireNonNull(close.apply(wo.open), "The publisher supplied is null"); } catch (Throwable e) { - cancelled = true; + cancel(); a.onError(e); continue; } @@ -323,36 +328,22 @@ static final class WindowOperation<T, B> { static final class OperatorWindowBoundaryOpenSubscriber<T, B> extends DisposableSubscriber<B> { final WindowBoundaryMainSubscriber<T, B, ?> parent; - boolean done; - OperatorWindowBoundaryOpenSubscriber(WindowBoundaryMainSubscriber<T, B, ?> parent) { this.parent = parent; } @Override public void onNext(B t) { - if (done) { - return; - } parent.open(t); } @Override public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - done = true; parent.error(t); } @Override public void onComplete() { - if (done) { - return; - } - done = true; parent.onComplete(); } } @@ -370,12 +361,8 @@ static final class OperatorWindowBoundaryCloseSubscriber<T, V> extends Disposabl @Override public void onNext(V t) { - if (done) { - return; - } - done = true; cancel(); - parent.close(this); + onComplete(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java index 5286407e7d..f82c56a889 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java @@ -18,201 +18,224 @@ import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; -import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.fuseable.SimplePlainQueue; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.subscribers.QueueDrainSubscriber; import io.reactivex.internal.subscriptions.SubscriptionHelper; -import io.reactivex.internal.util.NotificationLite; +import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.UnicastProcessor; -import io.reactivex.subscribers.*; +import io.reactivex.subscribers.DisposableSubscriber; public final class FlowableWindowBoundarySupplier<T, B> extends AbstractFlowableWithUpstream<T, Flowable<T>> { final Callable<? extends Publisher<B>> other; - final int bufferSize; + final int capacityHint; public FlowableWindowBoundarySupplier(Flowable<T> source, - Callable<? extends Publisher<B>> other, int bufferSize) { + Callable<? extends Publisher<B>> other, int capacityHint) { super(source); this.other = other; - this.bufferSize = bufferSize; + this.capacityHint = capacityHint; } @Override - protected void subscribeActual(Subscriber<? super Flowable<T>> s) { - source.subscribe(new WindowBoundaryMainSubscriber<T, B>( - new SerializedSubscriber<Flowable<T>>(s), other, bufferSize)); + protected void subscribeActual(Subscriber<? super Flowable<T>> subscriber) { + WindowBoundaryMainSubscriber<T, B> parent = new WindowBoundaryMainSubscriber<T, B>(subscriber, capacityHint, other); + + source.subscribe(parent); } static final class WindowBoundaryMainSubscriber<T, B> - extends QueueDrainSubscriber<T, Object, Flowable<T>> - implements Subscription { + extends AtomicInteger + implements FlowableSubscriber<T>, Subscription, Runnable { - final Callable<? extends Publisher<B>> other; - final int bufferSize; + private static final long serialVersionUID = 2233020065421370272L; - Subscription s; + final Subscriber<? super Flowable<T>> downstream; - final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); + final int capacityHint; - UnicastProcessor<T> window; + final AtomicReference<WindowBoundaryInnerSubscriber<T, B>> boundarySubscriber; - static final Object NEXT = new Object(); + static final WindowBoundaryInnerSubscriber<Object, Object> BOUNDARY_DISPOSED = new WindowBoundaryInnerSubscriber<Object, Object>(null); - final AtomicLong windows = new AtomicLong(); + final AtomicInteger windows; - WindowBoundaryMainSubscriber(Subscriber<? super Flowable<T>> actual, Callable<? extends Publisher<B>> other, - int bufferSize) { - super(actual, new MpscLinkedQueue<Object>()); - this.other = other; - this.bufferSize = bufferSize; - windows.lazySet(1); - } + final MpscLinkedQueue<Object> queue; - @Override - public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + final AtomicThrowable errors; - Subscriber<? super Flowable<T>> a = actual; - a.onSubscribe(this); + final AtomicBoolean stopWindows; - if (cancelled) { - return; - } + final Callable<? extends Publisher<B>> other; - Publisher<B> p; + static final Object NEXT_WINDOW = new Object(); - try { - p = ObjectHelper.requireNonNull(other.call(), "The first window publisher supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - s.cancel(); - a.onError(e); - return; - } + final AtomicLong requested; - UnicastProcessor<T> w = UnicastProcessor.<T>create(bufferSize); + Subscription upstream; - long r = requested(); - if (r != 0L) { - a.onNext(w); - if (r != Long.MAX_VALUE) { - produced(1); - } - } else { - s.cancel(); - a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests")); - return; - } + volatile boolean done; - window = w; + UnicastProcessor<T> window; - WindowBoundaryInnerSubscriber<T, B> inner = new WindowBoundaryInnerSubscriber<T, B>(this); + long emitted; - if (boundary.compareAndSet(null, inner)) { - windows.getAndIncrement(); - s.request(Long.MAX_VALUE); - p.subscribe(inner); - } - } + WindowBoundaryMainSubscriber(Subscriber<? super Flowable<T>> downstream, int capacityHint, Callable<? extends Publisher<B>> other) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundarySubscriber = new AtomicReference<WindowBoundaryInnerSubscriber<T, B>>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue<Object>(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.other = other; + this.requested = new AtomicLong(); } @Override - public void onNext(T t) { - if (done) { - return; + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + queue.offer(NEXT_WINDOW); + drain(); + s.request(Long.MAX_VALUE); } - if (fastEnter()) { - UnicastProcessor<T> w = window; + } - w.onNext(t); + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } - if (leave(-1) == 0) { - return; - } + @Override + public void onError(Throwable e) { + disposeBoundary(); + if (errors.addThrowable(e)) { + done = true; + drain(); } else { - queue.offer(NotificationLite.next(t)); - if (!enter()) { - return; - } + RxJavaPlugins.onError(e); } - drainLoop(); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - error = t; + public void onComplete() { + disposeBoundary(); done = true; - if (enter()) { - drainLoop(); - } + drain(); + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + @Override + public void cancel() { + if (stopWindows.compareAndSet(false, true)) { + disposeBoundary(); + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } } - - actual.onError(t); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - if (enter()) { - drainLoop(); + public void request(long n) { + BackpressureHelper.add(requested, n); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + void disposeBoundary() { + Disposable d = boundarySubscriber.getAndSet((WindowBoundaryInnerSubscriber)BOUNDARY_DISPOSED); + if (d != null && d != BOUNDARY_DISPOSED) { + d.dispose(); } + } + @Override + public void run() { if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + upstream.cancel(); } + } - actual.onComplete(); - + void innerNext(WindowBoundaryInnerSubscriber<T, B> sender) { + boundarySubscriber.compareAndSet(sender, null); + queue.offer(NEXT_WINDOW); + drain(); } - @Override - public void request(long n) { - requested(n); + void innerError(Throwable e) { + upstream.cancel(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } } - @Override - public void cancel() { - cancelled = true; + void innerComplete() { + upstream.cancel(); + done = true; + drain(); } - void drainLoop() { - final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; - UnicastProcessor<T> w = window; + Subscriber<? super Flowable<T>> downstream = this.downstream; + MpscLinkedQueue<Object> queue = this.queue; + AtomicThrowable errors = this.errors; + long emitted = this.emitted; + for (;;) { for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastProcessor<T> w = window; + boolean d = done; - Object o = q.poll(); + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); - boolean empty = o == null; + boolean empty = v == null; if (d && empty) { - DisposableHelper.dispose(boundary); - Throwable e = error; - if (e != null) { - w.onError(e); + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); } else { - w.onComplete(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); } return; } @@ -221,73 +244,57 @@ void drainLoop() { break; } - if (o == NEXT) { + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } + + if (w != null) { + window = null; w.onComplete(); + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); - return; - } + if (!stopWindows.get()) { + if (emitted != requested.get()) { + w = UnicastProcessor.create(capacityHint, this); + window = w; + windows.getAndIncrement(); - if (cancelled) { - continue; - } + Publisher<B> otherSource; - Publisher<B> p; + try { + otherSource = ObjectHelper.requireNonNull(other.call(), "The other Callable returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + done = true; + continue; + } - try { - p = ObjectHelper.requireNonNull(other.call(), "The publisher supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - DisposableHelper.dispose(boundary); - a.onError(e); - return; - } + WindowBoundaryInnerSubscriber<T, B> bo = new WindowBoundaryInnerSubscriber<T, B>(this); - w = UnicastProcessor.<T>create(bufferSize); + if (boundarySubscriber.compareAndSet(null, bo)) { + otherSource.subscribe(bo); - long r = requested(); - if (r != 0L) { - windows.getAndIncrement(); - - a.onNext(w); - if (r != Long.MAX_VALUE) { - produced(1); + emitted++; + downstream.onNext(w); } } else { - // don't emit new windows - cancelled = true; - a.onError(new MissingBackpressureException("Could not deliver new window due to lack of requests")); - continue; - } - - window = w; - - WindowBoundaryInnerSubscriber<T, B> b = new WindowBoundaryInnerSubscriber<T, B>(this); - - if (boundary.compareAndSet(boundary.get(), b)) { - p.subscribe(b); + upstream.cancel(); + disposeBoundary(); + errors.addThrowable(new MissingBackpressureException("Could not deliver a window due to lack of requests")); + done = true; } - - continue; } - - w.onNext(NotificationLite.<T>getValue(o)); } - missed = leave(-missed); + this.emitted = emitted; + missed = addAndGet(-missed); if (missed == 0) { - return; + break; } } } - - void next() { - queue.offer(NEXT); - if (enter()) { - drainLoop(); - } - } } static final class WindowBoundaryInnerSubscriber<T, B> extends DisposableSubscriber<B> { @@ -305,8 +312,8 @@ public void onNext(B t) { return; } done = true; - cancel(); - parent.next(); + dispose(); + parent.innerNext(this); } @Override @@ -316,7 +323,7 @@ public void onError(Throwable t) { return; } done = true; - parent.onError(t); + parent.innerError(t); } @Override @@ -325,8 +332,7 @@ public void onComplete() { return; } done = true; - parent.onComplete(); -// parent.next(); + parent.innerComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java index f0bd22429f..2db0eba8cc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java @@ -82,7 +82,7 @@ static final class WindowExactUnboundedSubscriber<T> final Scheduler scheduler; final int bufferSize; - Subscription s; + Subscription upstream; UnicastProcessor<T> window; @@ -103,12 +103,12 @@ static final class WindowExactUnboundedSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; window = UnicastProcessor.<T>create(bufferSize); - Subscriber<? super Flowable<T>> a = actual; + Subscriber<? super Flowable<T>> a = downstream; a.onSubscribe(this); long r = requested(); @@ -159,8 +159,7 @@ public void onError(Throwable t) { drainLoop(); } - actual.onError(t); - dispose(); + downstream.onError(t); } @Override @@ -170,8 +169,7 @@ public void onComplete() { drainLoop(); } - actual.onComplete(); - dispose(); + downstream.onComplete(); } @Override @@ -184,28 +182,21 @@ public void cancel() { cancelled = true; } - public void dispose() { - DisposableHelper.dispose(timer); - } - @Override public void run() { - if (cancelled) { terminated = true; - dispose(); } queue.offer(NEXT); if (enter()) { drainLoop(); } - } void drainLoop() { final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + final Subscriber<? super Flowable<T>> a = downstream; UnicastProcessor<T> w = window; int missed = 1; @@ -221,13 +212,13 @@ void drainLoop() { if (d && (o == null || o == NEXT)) { window = null; q.clear(); - dispose(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + timer.dispose(); return; } @@ -250,13 +241,13 @@ void drainLoop() { } else { window = null; queue.clear(); - s.cancel(); - dispose(); + upstream.cancel(); a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); + timer.dispose(); return; } } else { - s.cancel(); + upstream.cancel(); } continue; } @@ -287,7 +278,7 @@ static final class WindowExactBoundedSubscriber<T> long producerIndex; - Subscription s; + Subscription upstream; UnicastProcessor<T> window; @@ -315,11 +306,11 @@ static final class WindowExactBoundedSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { - this.s = s; + this.upstream = s; - Subscriber<? super Flowable<T>> a = actual; + Subscriber<? super Flowable<T>> a = downstream; a.onSubscribe(this); @@ -343,15 +334,15 @@ public void onSubscribe(Subscription s) { return; } - Disposable d; + Disposable task; ConsumerIndexHolder consumerIndexHolder = new ConsumerIndexHolder(producerIndex, this); if (restartTimerOnMaxSize) { - d = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); + task = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); } else { - d = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); + task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); } - if (timer.replace(d)) { + if (timer.replace(task)) { s.request(Long.MAX_VALUE); } } @@ -380,7 +371,7 @@ public void onNext(T t) { if (r != 0L) { w = UnicastProcessor.<T>create(bufferSize); window = w; - actual.onNext(w); + downstream.onNext(w); if (r != Long.MAX_VALUE) { produced(1); } @@ -390,15 +381,13 @@ public void onNext(T t) { tm.dispose(); Disposable task = worker.schedulePeriodically( new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); - if (!timer.compareAndSet(tm, task)) { - task.dispose(); - } + timer.replace(task); } } else { window = null; - s.cancel(); - actual.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); - dispose(); + upstream.cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); + disposeTimer(); return; } } else { @@ -425,8 +414,7 @@ public void onError(Throwable t) { drainLoop(); } - actual.onError(t); - dispose(); + downstream.onError(t); } @Override @@ -436,8 +424,7 @@ public void onComplete() { drainLoop(); } - actual.onComplete(); - dispose(); + downstream.onComplete(); } @Override @@ -450,8 +437,8 @@ public void cancel() { cancelled = true; } - public void dispose() { - DisposableHelper.dispose(timer); + public void disposeTimer() { + timer.dispose(); Worker w = worker; if (w != null) { w.dispose(); @@ -460,7 +447,7 @@ public void dispose() { void drainLoop() { final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + final Subscriber<? super Flowable<T>> a = downstream; UnicastProcessor<T> w = window; int missed = 1; @@ -468,9 +455,9 @@ void drainLoop() { for (;;) { if (terminated) { - s.cancel(); + upstream.cancel(); q.clear(); - dispose(); + disposeTimer(); return; } @@ -490,7 +477,7 @@ void drainLoop() { } else { w.onComplete(); } - dispose(); + disposeTimer(); return; } @@ -500,7 +487,7 @@ void drainLoop() { if (isHolder) { ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; - if (restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { w.onComplete(); count = 0; w = UnicastProcessor.<T>create(bufferSize); @@ -515,9 +502,9 @@ void drainLoop() { } else { window = null; queue.clear(); - s.cancel(); + upstream.cancel(); a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); - dispose(); + disposeTimer(); return; } } @@ -538,7 +525,7 @@ void drainLoop() { if (r != 0L) { w = UnicastProcessor.<T>create(bufferSize); window = w; - actual.onNext(w); + downstream.onNext(w); if (r != Long.MAX_VALUE) { produced(1); } @@ -549,16 +536,14 @@ void drainLoop() { Disposable task = worker.schedulePeriodically( new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); - if (!timer.compareAndSet(tm, task)) { - task.dispose(); - } + timer.replace(task); } } else { window = null; - s.cancel(); - actual.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); - dispose(); + upstream.cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); + disposeTimer(); return; } } else { @@ -589,7 +574,6 @@ public void run() { p.queue.offer(this); } else { p.terminated = true; - p.dispose(); } if (p.enter()) { p.drainLoop(); @@ -609,7 +593,7 @@ static final class WindowSkipSubscriber<T> final List<UnicastProcessor<T>> windows; - Subscription s; + Subscription upstream; volatile boolean terminated; @@ -627,11 +611,11 @@ static final class WindowSkipSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { - this.s = s; + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (cancelled) { return; @@ -642,7 +626,7 @@ public void onSubscribe(Subscription s) { final UnicastProcessor<T> w = UnicastProcessor.<T>create(bufferSize); windows.add(w); - actual.onNext(w); + downstream.onNext(w); if (r != Long.MAX_VALUE) { produced(1); } @@ -654,7 +638,7 @@ public void onSubscribe(Subscription s) { } else { s.cancel(); - actual.onError(new MissingBackpressureException("Could not emit the first window due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not emit the first window due to lack of requests")); } } } @@ -685,8 +669,7 @@ public void onError(Throwable t) { drainLoop(); } - actual.onError(t); - dispose(); + downstream.onError(t); } @Override @@ -696,8 +679,7 @@ public void onComplete() { drainLoop(); } - actual.onComplete(); - dispose(); + downstream.onComplete(); } @Override @@ -710,10 +692,6 @@ public void cancel() { cancelled = true; } - public void dispose() { - worker.dispose(); - } - void complete(UnicastProcessor<T> w) { queue.offer(new SubjectWork<T>(w, false)); if (enter()) { @@ -724,7 +702,7 @@ void complete(UnicastProcessor<T> w) { @SuppressWarnings("unchecked") void drainLoop() { final SimplePlainQueue<Object> q = queue; - final Subscriber<? super Flowable<T>> a = actual; + final Subscriber<? super Flowable<T>> a = downstream; final List<UnicastProcessor<T>> ws = windows; int missed = 1; @@ -733,10 +711,10 @@ void drainLoop() { for (;;) { if (terminated) { - s.cancel(); - dispose(); + upstream.cancel(); q.clear(); ws.clear(); + worker.dispose(); return; } @@ -760,7 +738,7 @@ void drainLoop() { } } ws.clear(); - dispose(); + worker.dispose(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java index 4448a7f038..00017d431d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java @@ -21,6 +21,7 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.BiFunction; import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.subscribers.SerializedSubscriber; @@ -45,30 +46,40 @@ protected void subscribeActual(Subscriber<? super R> s) { source.subscribe(wlf); } - static final class WithLatestFromSubscriber<T, U, R> extends AtomicReference<U> implements FlowableSubscriber<T>, Subscription { + static final class WithLatestFromSubscriber<T, U, R> extends AtomicReference<U> + implements ConditionalSubscriber<T>, Subscription { private static final long serialVersionUID = -312246233408980075L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; + final BiFunction<? super T, ? super U, ? extends R> combiner; - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + final AtomicReference<Subscription> upstream = new AtomicReference<Subscription>(); final AtomicLong requested = new AtomicLong(); final AtomicReference<Subscription> other = new AtomicReference<Subscription>(); WithLatestFromSubscriber(Subscriber<? super R> actual, BiFunction<? super T, ? super U, ? extends R> combiner) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; } + @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } @Override public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.get().request(1); + } + } + + @Override + public boolean tryOnNext(T t) { U u = get(); if (u != null) { R r; @@ -77,33 +88,36 @@ public void onNext(T t) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancel(); - actual.onError(e); - return; + downstream.onError(e); + return false; } - actual.onNext(r); + downstream.onNext(r); + return true; + } else { + return false; } } @Override public void onError(Throwable t) { SubscriptionHelper.cancel(other); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { SubscriptionHelper.cancel(other); - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); SubscriptionHelper.cancel(other); } @@ -112,8 +126,8 @@ public boolean setOther(Subscription o) { } public void otherError(Throwable e) { - SubscriptionHelper.cancel(s); - actual.onError(e); + SubscriptionHelper.cancel(upstream); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java index 2108dadf1a..6d6b949c33 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java @@ -19,10 +19,10 @@ import io.reactivex.*; import io.reactivex.annotations.*; -import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; import io.reactivex.internal.subscriptions.*; import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; @@ -95,11 +95,11 @@ protected void subscribeActual(Subscriber<? super R> s) { static final class WithLatestFromSubscriber<T, R> extends AtomicInteger - implements FlowableSubscriber<T>, Subscription { + implements ConditionalSubscriber<T>, Subscription { private static final long serialVersionUID = 1577321883966341961L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super Object[], R> combiner; @@ -107,7 +107,7 @@ static final class WithLatestFromSubscriber<T, R> final AtomicReferenceArray<Object> values; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; final AtomicLong requested; @@ -116,7 +116,7 @@ static final class WithLatestFromSubscriber<T, R> volatile boolean done; WithLatestFromSubscriber(Subscriber<? super R> actual, Function<? super Object[], R> combiner, int n) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; WithLatestInnerSubscriber[] s = new WithLatestInnerSubscriber[n]; for (int i = 0; i < n; i++) { @@ -124,16 +124,16 @@ static final class WithLatestFromSubscriber<T, R> } this.subscribers = s; this.values = new AtomicReferenceArray<Object>(n); - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.requested = new AtomicLong(); this.error = new AtomicThrowable(); } void subscribe(Publisher<?>[] others, int n) { WithLatestInnerSubscriber[] subscribers = this.subscribers; - AtomicReference<Subscription> s = this.s; + AtomicReference<Subscription> upstream = this.upstream; for (int i = 0; i < n; i++) { - if (SubscriptionHelper.isCancelled(s.get()) || done) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { return; } others[i].subscribe(subscribers[i]); @@ -142,13 +142,20 @@ void subscribe(Publisher<?>[] others, int n) { @Override public void onSubscribe(Subscription s) { - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } @Override public void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.get().request(1); + } + } + + @Override + public boolean tryOnNext(T t) { if (done) { - return; + return false; } AtomicReferenceArray<Object> ara = values; int n = ara.length(); @@ -159,8 +166,7 @@ public void onNext(T t) { Object o = ara.get(i); if (o == null) { // somebody hasn't signalled yet, skip this T - s.get().request(1); - return; + return false; } objects[i + 1] = o; } @@ -173,10 +179,11 @@ public void onNext(T t) { Exceptions.throwIfFatal(ex); cancel(); onError(ex); - return; + return false; } - HalfSerializer.onNext(actual, v, this, error); + HalfSerializer.onNext(downstream, v, this, error); + return true; } @Override @@ -187,7 +194,7 @@ public void onError(Throwable t) { } done = true; cancelAllBut(-1); - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } @Override @@ -195,19 +202,19 @@ public void onComplete() { if (!done) { done = true; cancelAllBut(-1); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } } @Override public void request(long n) { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } @Override public void cancel() { - SubscriptionHelper.cancel(s); - for (Disposable s : subscribers) { + SubscriptionHelper.cancel(upstream); + for (WithLatestInnerSubscriber s : subscribers) { s.dispose(); } } @@ -218,16 +225,17 @@ void innerNext(int index, Object o) { void innerError(int index, Throwable t) { done = true; - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); cancelAllBut(index); - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } void innerComplete(int index, boolean nonEmpty) { if (!nonEmpty) { done = true; + SubscriptionHelper.cancel(upstream); cancelAllBut(index); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } } @@ -243,7 +251,7 @@ void cancelAllBut(int index) { static final class WithLatestInnerSubscriber extends AtomicReference<Subscription> - implements FlowableSubscriber<Object>, Disposable { + implements FlowableSubscriber<Object> { private static final long serialVersionUID = 3256684027868224024L; @@ -260,9 +268,7 @@ static final class WithLatestInnerSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -283,13 +289,7 @@ public void onComplete() { parent.innerComplete(index, hasValue); } - @Override - public boolean isDisposed() { - return SubscriptionHelper.isCancelled(get()); - } - - @Override - public void dispose() { + void dispose() { SubscriptionHelper.cancel(this); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java index c154c2e820..b8516a93e9 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java @@ -83,10 +83,9 @@ static final class ZipCoordinator<T, R> extends AtomicInteger implements Subscription { - private static final long serialVersionUID = -2434867452883857743L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final ZipSubscriber<T, R>[] subscribers; @@ -104,7 +103,7 @@ static final class ZipCoordinator<T, R> ZipCoordinator(Subscriber<? super R> actual, Function<? super Object[], ? extends R> zipper, int n, int prefetch, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.zipper = zipper; this.delayErrors = delayErrors; @SuppressWarnings("unchecked") @@ -166,7 +165,7 @@ void drain() { return; } - final Subscriber<? super R> a = actual; + final Subscriber<? super R> a = downstream; final ZipSubscriber<T, R>[] qs = subscribers; final int n = qs.length; Object[] values = current; @@ -320,7 +319,6 @@ void drain() { } } - static final class ZipSubscriber<T, R> extends AtomicReference<Subscription> implements FlowableSubscriber<T>, Subscription { private static final long serialVersionUID = -4627193790118206028L; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java index ffec85365d..7a81c2dcba 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java @@ -67,26 +67,26 @@ public void subscribeActual(Subscriber<? super V> t) { } static final class ZipIterableSubscriber<T, U, V> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super V> actual; + final Subscriber<? super V> downstream; final Iterator<U> iterator; final BiFunction<? super T, ? super U, ? extends V> zipper; - Subscription s; + Subscription upstream; boolean done; ZipIterableSubscriber(Subscriber<? super V> actual, Iterator<U> iterator, BiFunction<? super T, ? super U, ? extends V> zipper) { - this.actual = actual; + this.downstream = actual; this.iterator = iterator; this.zipper = zipper; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -113,7 +113,7 @@ public void onNext(T t) { return; } - actual.onNext(v); + downstream.onNext(v); boolean b; @@ -126,16 +126,16 @@ public void onNext(T t) { if (!b) { done = true; - s.cancel(); - actual.onComplete(); + upstream.cancel(); + downstream.onComplete(); } } void error(Throwable e) { Exceptions.throwIfFatal(e); done = true; - s.cancel(); - actual.onError(e); + upstream.cancel(); + downstream.onError(e); } @Override @@ -145,7 +145,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -154,17 +154,17 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java index 9087d2ed76..8efc69b24b 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java @@ -64,77 +64,76 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { count = sources.length; } - AmbMaybeObserver<T> parent = new AmbMaybeObserver<T>(observer); - observer.onSubscribe(parent); + CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + AtomicBoolean winner = new AtomicBoolean(); for (int i = 0; i < count; i++) { MaybeSource<? extends T> s = sources[i]; - if (parent.isDisposed()) { + if (set.isDisposed()) { return; } if (s == null) { - parent.onError(new NullPointerException("One of the MaybeSources is null")); + set.dispose(); + NullPointerException ex = new NullPointerException("One of the MaybeSources is null"); + if (winner.compareAndSet(false, true)) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } return; } - s.subscribe(parent); + s.subscribe(new AmbMaybeObserver<T>(observer, set, winner)); } if (count == 0) { observer.onComplete(); } - } static final class AmbMaybeObserver<T> - extends AtomicBoolean - implements MaybeObserver<T>, Disposable { + implements MaybeObserver<T> { + final MaybeObserver<? super T> downstream; - private static final long serialVersionUID = -7044685185359438206L; - - final MaybeObserver<? super T> actual; + final AtomicBoolean winner; final CompositeDisposable set; - AmbMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; - this.set = new CompositeDisposable(); - } + Disposable upstream; - @Override - public void dispose() { - if (compareAndSet(false, true)) { - set.dispose(); - } - } - - @Override - public boolean isDisposed() { - return get(); + AmbMaybeObserver(MaybeObserver<? super T> downstream, CompositeDisposable set, AtomicBoolean winner) { + this.downstream = downstream; + this.set = set; + this.winner = winner; } @Override public void onSubscribe(Disposable d) { + upstream = d; set.add(d); } @Override public void onSuccess(T value) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - actual.onSuccess(value); + downstream.onSuccess(value); } } @Override public void onError(Throwable e) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -142,12 +141,12 @@ public void onError(Throwable e) { @Override public void onComplete() { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - actual.onComplete(); + downstream.onComplete(); } } - } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java index fd7747f18b..c8adbd7ab8 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java @@ -89,7 +89,7 @@ public void onSuccess(T value) { this.value = value; for (CacheDisposable<T> inner : observers.getAndSet(TERMINATED)) { if (!inner.isDisposed()) { - inner.actual.onSuccess(value); + inner.downstream.onSuccess(value); } } } @@ -100,7 +100,7 @@ public void onError(Throwable e) { this.error = e; for (CacheDisposable<T> inner : observers.getAndSet(TERMINATED)) { if (!inner.isDisposed()) { - inner.actual.onError(e); + inner.downstream.onError(e); } } } @@ -110,7 +110,7 @@ public void onError(Throwable e) { public void onComplete() { for (CacheDisposable<T> inner : observers.getAndSet(TERMINATED)) { if (!inner.isDisposed()) { - inner.actual.onComplete(); + inner.downstream.onComplete(); } } } @@ -175,11 +175,11 @@ static final class CacheDisposable<T> private static final long serialVersionUID = -5791853038359966195L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; CacheDisposable(MaybeObserver<? super T> actual, MaybeCache<T> parent) { super(parent); - this.actual = actual; + this.downstream = actual; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java index 6b440732f4..9dc56e137f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java @@ -20,6 +20,8 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; import io.reactivex.plugins.RxJavaPlugins; /** @@ -29,8 +31,7 @@ */ public final class MaybeCallbackObserver<T> extends AtomicReference<Disposable> -implements MaybeObserver<T>, Disposable { - +implements MaybeObserver<T>, Disposable, LambdaConsumerIntrospection { private static final long serialVersionUID = -6076952298809384986L; @@ -96,5 +97,8 @@ public void onComplete() { } } - + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java index b67534ade9..0584c47b45 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java @@ -49,7 +49,7 @@ static final class ConcatMaybeObserver<T> private static final long serialVersionUID = 3520831347801429610L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicLong requested; @@ -64,7 +64,7 @@ static final class ConcatMaybeObserver<T> long produced; ConcatMaybeObserver(Subscriber<? super T> actual, MaybeSource<? extends T>[] sources) { - this.actual = actual; + this.downstream = actual; this.sources = sources; this.requested = new AtomicLong(); this.disposables = new SequentialDisposable(); @@ -97,7 +97,7 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override @@ -113,7 +113,7 @@ void drain() { } AtomicReference<Object> c = current; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; Disposable cancelled = disposables; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java index ebff9986d9..6c2e1604ab 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java @@ -51,7 +51,7 @@ static final class ConcatMaybeObserver<T> private static final long serialVersionUID = 3520831347801429610L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicLong requested; @@ -68,7 +68,7 @@ static final class ConcatMaybeObserver<T> long produced; ConcatMaybeObserver(Subscriber<? super T> actual, MaybeSource<? extends T>[] sources) { - this.actual = actual; + this.downstream = actual; this.sources = sources; this.requested = new AtomicLong(); this.disposables = new SequentialDisposable(); @@ -123,7 +123,7 @@ void drain() { } AtomicReference<Object> c = current; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; Disposable cancelled = disposables; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java index 1a3c286c71..353afadf83 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java @@ -63,7 +63,7 @@ static final class ConcatMaybeObserver<T> private static final long serialVersionUID = 3520831347801429610L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicLong requested; @@ -76,7 +76,7 @@ static final class ConcatMaybeObserver<T> long produced; ConcatMaybeObserver(Subscriber<? super T> actual, Iterator<? extends MaybeSource<? extends T>> sources) { - this.actual = actual; + this.downstream = actual; this.sources = sources; this.requested = new AtomicLong(); this.disposables = new SequentialDisposable(); @@ -109,7 +109,7 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override @@ -125,7 +125,7 @@ void drain() { } AtomicReference<Object> c = current; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; Disposable cancelled = disposables; for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java index 3399e84996..aa167b2a08 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java @@ -48,52 +48,52 @@ protected void subscribeActual(SingleObserver<? super Boolean> observer) { static final class ContainsMaybeObserver implements MaybeObserver<Object>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final Object value; - Disposable d; + Disposable upstream; ContainsMaybeObserver(SingleObserver<? super Boolean> actual, Object value) { - this.actual = actual; + this.downstream = actual; this.value = value; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onSuccess(Object value) { - d = DisposableHelper.DISPOSED; - actual.onSuccess(ObjectHelper.equals(value, this.value)); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(ObjectHelper.equals(value, this.value)); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onSuccess(false); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(false); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java index 63d8880d42..df36c7d755 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java @@ -42,50 +42,50 @@ protected void subscribeActual(SingleObserver<? super Long> observer) { } static final class CountMaybeObserver implements MaybeObserver<Object>, Disposable { - final SingleObserver<? super Long> actual; + final SingleObserver<? super Long> downstream; - Disposable d; + Disposable upstream; - CountMaybeObserver(SingleObserver<? super Long> actual) { - this.actual = actual; + CountMaybeObserver(SingleObserver<? super Long> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(Object value) { - d = DisposableHelper.DISPOSED; - actual.onSuccess(1L); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(1L); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onSuccess(0L); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(0L); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java index 21f06e2109..e328a2b3c7 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java @@ -37,9 +37,9 @@ public MaybeCreate(MaybeOnSubscribe<T> source) { } @Override - protected void subscribeActual(MaybeObserver<? super T> s) { - Emitter<T> parent = new Emitter<T>(s); - s.onSubscribe(parent); + protected void subscribeActual(MaybeObserver<? super T> observer) { + Emitter<T> parent = new Emitter<T>(observer); + observer.onSubscribe(parent); try { source.subscribe(parent); @@ -53,13 +53,12 @@ static final class Emitter<T> extends AtomicReference<Disposable> implements MaybeEmitter<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Emitter(MaybeObserver<? super T> actual) { - this.actual = actual; + Emitter(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } - private static final long serialVersionUID = -2467358622224974244L; @Override @@ -69,9 +68,9 @@ public void onSuccess(T value) { if (d != DisposableHelper.DISPOSED) { try { if (value == null) { - actual.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); + downstream.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); } else { - actual.onSuccess(value); + downstream.onSuccess(value); } } finally { if (d != null) { @@ -98,7 +97,7 @@ public boolean tryOnError(Throwable t) { Disposable d = getAndSet(DisposableHelper.DISPOSED); if (d != DisposableHelper.DISPOSED) { try { - actual.onError(t); + downstream.onError(t); } finally { if (d != null) { d.dispose(); @@ -116,7 +115,7 @@ public void onComplete() { Disposable d = getAndSet(DisposableHelper.DISPOSED); if (d != DisposableHelper.DISPOSED) { try { - actual.onComplete(); + downstream.onComplete(); } finally { if (d != null) { d.dispose(); @@ -145,5 +144,10 @@ public void dispose() { public boolean isDisposed() { return DisposableHelper.isDisposed(get()); } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java index 1cc31495ed..68a1508195 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java @@ -51,7 +51,7 @@ static final class DelayMaybeObserver<T> private static final long serialVersionUID = 5566860102500855068L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final long delay; @@ -64,7 +64,7 @@ static final class DelayMaybeObserver<T> Throwable error; DelayMaybeObserver(MaybeObserver<? super T> actual, long delay, TimeUnit unit, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.delay = delay; this.unit = unit; this.scheduler = scheduler; @@ -74,13 +74,13 @@ static final class DelayMaybeObserver<T> public void run() { Throwable ex = error; if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { T v = value; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } } @@ -98,7 +98,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java index b14b17a671..d3a0f783e2 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java @@ -49,7 +49,7 @@ static final class DelayMaybeObserver<T, U> final Publisher<U> otherSource; - Disposable d; + Disposable upstream; DelayMaybeObserver(MaybeObserver<? super T> actual, Publisher<U> otherSource) { this.other = new OtherSubscriber<T>(actual); @@ -58,42 +58,42 @@ static final class DelayMaybeObserver<T, U> @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; SubscriptionHelper.cancel(other); } @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(other.get()); + return other.get() == SubscriptionHelper.CANCELLED; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - other.actual.onSubscribe(this); + other.downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; other.value = value; subscribeNext(); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; other.error = e; subscribeNext(); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; subscribeNext(); } @@ -108,21 +108,19 @@ static final class OtherSubscriber<T> extends private static final long serialVersionUID = -1215060610805418006L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; T value; Throwable error; - OtherSubscriber(MaybeObserver<? super T> actual) { - this.actual = actual; + OtherSubscriber(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -139,9 +137,9 @@ public void onNext(Object t) { public void onError(Throwable t) { Throwable e = error; if (e == null) { - actual.onError(t); + downstream.onError(t); } else { - actual.onError(new CompositeException(e, t)); + downstream.onError(new CompositeException(e, t)); } } @@ -149,13 +147,13 @@ public void onError(Throwable t) { public void onComplete() { Throwable e = error; if (e != null) { - actual.onError(e); + downstream.onError(e); } else { T v = value; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java index 9a52d4af7c..c29fb3fc0a 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java @@ -48,7 +48,7 @@ static final class OtherSubscriber<T> implements FlowableSubscriber<Object>, Dis MaybeSource<T> source; - Subscription s; + Subscription upstream; OtherSubscriber(MaybeObserver<? super T> actual, MaybeSource<T> source) { this.main = new DelayMaybeObserver<T>(actual); @@ -57,10 +57,10 @@ static final class OtherSubscriber<T> implements FlowableSubscriber<Object>, Dis @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - main.actual.onSubscribe(this); + main.downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -68,9 +68,9 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Object t) { - if (s != SubscriptionHelper.CANCELLED) { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + if (upstream != SubscriptionHelper.CANCELLED) { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; subscribeNext(); } @@ -78,10 +78,10 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - if (s != SubscriptionHelper.CANCELLED) { - s = SubscriptionHelper.CANCELLED; + if (upstream != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; - main.actual.onError(t); + main.downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -89,8 +89,8 @@ public void onError(Throwable t) { @Override public void onComplete() { - if (s != SubscriptionHelper.CANCELLED) { - s = SubscriptionHelper.CANCELLED; + if (upstream != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; subscribeNext(); } @@ -110,8 +110,8 @@ public boolean isDisposed() { @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; DisposableHelper.dispose(main); } } @@ -121,10 +121,10 @@ static final class DelayMaybeObserver<T> extends AtomicReference<Disposable> private static final long serialVersionUID = 706635022205076709L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - DelayMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + DelayMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override @@ -134,17 +134,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java index 37dd45fade..da9ff60e81 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java @@ -34,8 +34,8 @@ public MaybeDelayWithCompletable(MaybeSource<T> source, CompletableSource other) } @Override - protected void subscribeActual(MaybeObserver<? super T> subscriber) { - other.subscribe(new OtherObserver<T>(subscriber, source)); + protected void subscribeActual(MaybeObserver<? super T> observer) { + other.subscribe(new OtherObserver<T>(observer, source)); } static final class OtherObserver<T> @@ -43,12 +43,12 @@ static final class OtherObserver<T> implements CompletableObserver, Disposable { private static final long serialVersionUID = 703409937383992161L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final MaybeSource<T> source; OtherObserver(MaybeObserver<? super T> actual, MaybeSource<T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; } @@ -56,18 +56,18 @@ static final class OtherObserver<T> public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - source.subscribe(new DelayWithMainObserver<T>(this, actual)); + source.subscribe(new DelayWithMainObserver<T>(this, downstream)); } @Override @@ -85,11 +85,11 @@ static final class DelayWithMainObserver<T> implements MaybeObserver<T> { final AtomicReference<Disposable> parent; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - DelayWithMainObserver(AtomicReference<Disposable> parent, MaybeObserver<? super T> actual) { + DelayWithMainObserver(AtomicReference<Disposable> parent, MaybeObserver<? super T> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -99,17 +99,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java index a0e098cc85..c6da27b3bd 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java @@ -35,58 +35,61 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class DetachMaybeObserver<T> implements MaybeObserver<T>, Disposable { - MaybeObserver<? super T> actual; + MaybeObserver<? super T> downstream; - Disposable d; + Disposable upstream; - DetachMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + DetachMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - actual = null; - d.dispose(); - d = DisposableHelper.DISPOSED; + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - MaybeObserver<? super T> a = actual; + upstream = DisposableHelper.DISPOSED; + MaybeObserver<? super T> a = downstream; if (a != null) { + downstream = null; a.onSuccess(value); } } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - MaybeObserver<? super T> a = actual; + upstream = DisposableHelper.DISPOSED; + MaybeObserver<? super T> a = downstream; if (a != null) { + downstream = null; a.onError(e); } } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - MaybeObserver<? super T> a = actual; + upstream = DisposableHelper.DISPOSED; + MaybeObserver<? super T> a = downstream; if (a != null) { + downstream = null; a.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java index d6045032ba..e87790321f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.maybe; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Consumer; @@ -23,10 +22,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class MaybeDoAfterSuccess<T> extends AbstractMaybeWithUpstream<T, T> { final Consumer<? super T> onAfterSuccess; @@ -37,35 +36,35 @@ public MaybeDoAfterSuccess(MaybeSource<T> source, Consumer<? super T> onAfterSuc } @Override - protected void subscribeActual(MaybeObserver<? super T> s) { - source.subscribe(new DoAfterObserver<T>(s, onAfterSuccess)); + protected void subscribeActual(MaybeObserver<? super T> observer) { + source.subscribe(new DoAfterObserver<T>(observer, onAfterSuccess)); } static final class DoAfterObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Consumer<? super T> onAfterSuccess; - Disposable d; + Disposable upstream; DoAfterObserver(MaybeObserver<? super T> actual, Consumer<? super T> onAfterSuccess) { - this.actual = actual; + this.downstream = actual; this.onAfterSuccess = onAfterSuccess; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T t) { - actual.onSuccess(t); + downstream.onSuccess(t); try { onAfterSuccess.accept(t); @@ -78,22 +77,22 @@ public void onSuccess(T t) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java index 354f98270f..7b5573661f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Action; @@ -25,11 +24,10 @@ /** * Execute an action after an onSuccess, onError, onComplete or a dispose event. - * + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class MaybeDoFinally<T> extends AbstractMaybeWithUpstream<T, T> { final Action onFinally; @@ -40,61 +38,61 @@ public MaybeDoFinally(MaybeSource<T> source, Action onFinally) { } @Override - protected void subscribeActual(MaybeObserver<? super T> s) { - source.subscribe(new DoFinallyObserver<T>(s, onFinally)); + protected void subscribeActual(MaybeObserver<? super T> observer) { + source.subscribe(new DoFinallyObserver<T>(observer, onFinally)); } static final class DoFinallyObserver<T> extends AtomicInteger implements MaybeObserver<T>, Disposable { private static final long serialVersionUID = 4109457741734051389L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Action onFinally; - Disposable d; + Disposable upstream; DoFinallyObserver(MaybeObserver<? super T> actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T t) { - actual.onSuccess(t); + downstream.onSuccess(t); runFinally(); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); runFinally(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); runFinally(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } void runFinally() { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java index a4c624ebb1..4769ee79f4 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java @@ -40,55 +40,55 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } static final class DoOnEventMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final BiConsumer<? super T, ? super Throwable> onEvent; - Disposable d; + Disposable upstream; DoOnEventMaybeObserver(MaybeObserver<? super T> actual, BiConsumer<? super T, ? super Throwable> onEvent) { - this.actual = actual; + this.downstream = actual; this.onEvent = onEvent; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; try { onEvent.accept(value, null); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; try { onEvent.accept(null, e); @@ -97,22 +97,22 @@ public void onError(Throwable e) { e = new CompositeException(e, ex); } - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; try { onEvent.accept(null, null); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java new file mode 100644 index 0000000000..81d0d8af34 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class MaybeDoOnTerminate<T> extends Maybe<T> { + + final MaybeSource<T> source; + + final Action onTerminate; + + public MaybeDoOnTerminate(MaybeSource<T> source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(MaybeObserver<? super T> observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements MaybeObserver<T> { + final MaybeObserver<? super T> downstream; + + DoOnTerminate(MaybeObserver<? super T> observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + + @Override + public void onComplete() { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java index f2d69368e3..f5f83fbcfc 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java @@ -53,7 +53,7 @@ protected void subscribeActual(SingleObserver<? super Boolean> observer) { static final class EqualCoordinator<T> extends AtomicInteger implements Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final EqualObserver<T> observer1; @@ -63,7 +63,7 @@ static final class EqualCoordinator<T> EqualCoordinator(SingleObserver<? super Boolean> actual, BiPredicate<? super T, ? super T> isEqual) { super(2); - this.actual = actual; + this.downstream = actual; this.isEqual = isEqual; this.observer1 = new EqualObserver<T>(this); this.observer2 = new EqualObserver<T>(this); @@ -98,13 +98,13 @@ void done() { b = isEqual.test((T)o1, (T)o2); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(b); + downstream.onSuccess(b); } else { - actual.onSuccess(o1 == null && o2 == null); + downstream.onSuccess(o1 == null && o2 == null); } } } @@ -116,7 +116,7 @@ void error(EqualObserver<T> sender, Throwable ex) { } else { observer1.dispose(); } - actual.onError(ex); + downstream.onError(ex); } else { RxJavaPlugins.onError(ex); } @@ -127,7 +127,6 @@ static final class EqualObserver<T> extends AtomicReference<Disposable> implements MaybeObserver<T> { - private static final long serialVersionUID = -3031974433025990931L; final EqualCoordinator<T> parent; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java index b77b1459f8..763ad258e0 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java @@ -41,35 +41,35 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class FilterMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Predicate<? super T> predicate; - Disposable d; + Disposable upstream; FilterMaybeObserver(MaybeObserver<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void dispose() { - Disposable d = this.d; - this.d = DisposableHelper.DISPOSED; + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; d.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -81,28 +81,25 @@ public void onSuccess(T value) { b = predicate.test(value); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } if (b) { - actual.onSuccess(value); + downstream.onSuccess(value); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } - - } - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java index b1495f5cef..820bc8b7ae 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java @@ -42,35 +42,35 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class FilterMaybeObserver<T> implements SingleObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Predicate<? super T> predicate; - Disposable d; + Disposable upstream; FilterMaybeObserver(MaybeObserver<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void dispose() { - Disposable d = this.d; - this.d = DisposableHelper.DISPOSED; + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; d.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -82,20 +82,20 @@ public void onSuccess(T value) { b = predicate.test(value); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } if (b) { - actual.onSuccess(value); + downstream.onSuccess(value); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java index f7735d0a8e..2b9b8ec339 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java @@ -76,7 +76,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(inner, d)) { - inner.actual.onSubscribe(this); + inner.downstream.onSubscribe(this); } } @@ -88,7 +88,7 @@ public void onSuccess(T value) { next = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null MaybeSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - inner.actual.onError(ex); + inner.downstream.onError(ex); return; } @@ -100,12 +100,12 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - inner.actual.onError(e); + inner.downstream.onError(e); } @Override public void onComplete() { - inner.actual.onComplete(); + inner.downstream.onComplete(); } static final class InnerObserver<T, U, R> @@ -114,7 +114,7 @@ static final class InnerObserver<T, U, R> private static final long serialVersionUID = -2897979525538174559L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final BiFunction<? super T, ? super U, ? extends R> resultSelector; @@ -122,7 +122,7 @@ static final class InnerObserver<T, U, R> InnerObserver(MaybeObserver<? super R> actual, BiFunction<? super T, ? super U, ? extends R> resultSelector) { - this.actual = actual; + this.downstream = actual; this.resultSelector = resultSelector; } @@ -142,21 +142,21 @@ public void onSuccess(U value) { r = ObjectHelper.requireNonNull(resultSelector.apply(t, value), "The resultSelector returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(r); + downstream.onSuccess(r); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java index 6e2c9e1ce6..aa0a7f93bc 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java @@ -38,9 +38,9 @@ public MaybeFlatMapCompletable(MaybeSource<T> source, Function<? super T, ? exte } @Override - protected void subscribeActual(CompletableObserver s) { - FlatMapCompletableObserver<T> parent = new FlatMapCompletableObserver<T>(s, mapper); - s.onSubscribe(parent); + protected void subscribeActual(CompletableObserver observer) { + FlatMapCompletableObserver<T> parent = new FlatMapCompletableObserver<T>(observer, mapper); + observer.onSubscribe(parent); source.subscribe(parent); } @@ -50,13 +50,13 @@ static final class FlatMapCompletableObserver<T> private static final long serialVersionUID = -2177128922851101253L; - final CompletableObserver actual; + final CompletableObserver downstream; final Function<? super T, ? extends CompletableSource> mapper; FlatMapCompletableObserver(CompletableObserver actual, Function<? super T, ? extends CompletableSource> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -94,12 +94,12 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java index b93574e23a..f4d174d924 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java @@ -57,13 +57,13 @@ static final class FlatMapIterableObserver<T, R> private static final long serialVersionUID = -8938804753851907758L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; final AtomicLong requested; - Disposable d; + Disposable upstream; volatile Iterator<? extends R> it; @@ -73,17 +73,17 @@ static final class FlatMapIterableObserver<T, R> FlatMapIterableObserver(Subscriber<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.requested = new AtomicLong(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -97,12 +97,12 @@ public void onSuccess(T value) { has = iterator.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } if (!has) { - actual.onComplete(); + downstream.onComplete(); return; } @@ -112,13 +112,13 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override @@ -132,8 +132,8 @@ public void request(long n) { @Override public void cancel() { cancelled = true; - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } void fastPath(Subscriber<? super R> a, Iterator<? extends R> iterator) { @@ -158,7 +158,6 @@ void fastPath(Subscriber<? super R> a, Iterator<? extends R> iterator) { return; } - boolean b; try { @@ -181,7 +180,7 @@ void drain() { return; } - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; Iterator<? extends R> iterator = this.it; if (outputFused && iterator != null) { diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java index 23317717cb..5513eb5e8f 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java @@ -43,19 +43,19 @@ public MaybeFlatMapIterableObservable(MaybeSource<T> source, } @Override - protected void subscribeActual(Observer<? super R> s) { - source.subscribe(new FlatMapIterableObserver<T, R>(s, mapper)); + protected void subscribeActual(Observer<? super R> observer) { + source.subscribe(new FlatMapIterableObserver<T, R>(observer, mapper)); } static final class FlatMapIterableObserver<T, R> extends BasicQueueDisposable<R> implements MaybeObserver<T> { - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; - Disposable d; + Disposable upstream; volatile Iterator<? extends R> it; @@ -65,22 +65,22 @@ static final class FlatMapIterableObserver<T, R> FlatMapIterableObserver(Observer<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - Observer<? super R> a = actual; + Observer<? super R> a = downstream; Iterator<? extends R> iterator; boolean has; @@ -128,7 +128,6 @@ public void onSuccess(T value) { return; } - boolean b; try { @@ -148,20 +147,20 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { cancelled = true; - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java index f5c4ea8084..81eb167484 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java @@ -56,10 +56,9 @@ static final class FlatMapMaybeObserver<T, R> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = 4375739915521278546L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super T, ? extends MaybeSource<? extends R>> onSuccessMapper; @@ -67,13 +66,13 @@ static final class FlatMapMaybeObserver<T, R> final Callable<? extends MaybeSource<? extends R>> onCompleteSupplier; - Disposable d; + Disposable upstream; FlatMapMaybeObserver(MaybeObserver<? super R> actual, Function<? super T, ? extends MaybeSource<? extends R>> onSuccessMapper, Function<? super Throwable, ? extends MaybeSource<? extends R>> onErrorMapper, Callable<? extends MaybeSource<? extends R>> onCompleteSupplier) { - this.actual = actual; + this.downstream = actual; this.onSuccessMapper = onSuccessMapper; this.onErrorMapper = onErrorMapper; this.onCompleteSupplier = onCompleteSupplier; @@ -82,7 +81,7 @@ static final class FlatMapMaybeObserver<T, R> @Override public void dispose() { DisposableHelper.dispose(this); - d.dispose(); + upstream.dispose(); } @Override @@ -92,10 +91,10 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -107,7 +106,7 @@ public void onSuccess(T value) { source = ObjectHelper.requireNonNull(onSuccessMapper.apply(value), "The onSuccessMapper returned a null MaybeSource"); } catch (Exception ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } @@ -122,7 +121,7 @@ public void onError(Throwable e) { source = ObjectHelper.requireNonNull(onErrorMapper.apply(e), "The onErrorMapper returned a null MaybeSource"); } catch (Exception ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } @@ -137,7 +136,7 @@ public void onComplete() { source = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onCompleteSupplier returned a null MaybeSource"); } catch (Exception ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } @@ -153,17 +152,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java index 240cfa8015..af55a7a57d 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java @@ -43,8 +43,8 @@ public MaybeFlatMapSingle(MaybeSource<T> source, Function<? super T, ? extends S } @Override - protected void subscribeActual(SingleObserver<? super R> actual) { - source.subscribe(new FlatMapMaybeObserver<T, R>(actual, mapper)); + protected void subscribeActual(SingleObserver<? super R> downstream) { + source.subscribe(new FlatMapMaybeObserver<T, R>(downstream, mapper)); } static final class FlatMapMaybeObserver<T, R> @@ -53,12 +53,12 @@ static final class FlatMapMaybeObserver<T, R> private static final long serialVersionUID = 4827726964688405508L; - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; final Function<? super T, ? extends SingleSource<? extends R>> mapper; FlatMapMaybeObserver(SingleObserver<? super R> actual, Function<? super T, ? extends SingleSource<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -75,7 +75,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -92,18 +92,18 @@ public void onSuccess(T value) { } if (!isDisposed()) { - ss.subscribe(new FlatMapSingleObserver<R>(this, actual)); + ss.subscribe(new FlatMapSingleObserver<R>(this, downstream)); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } @@ -111,11 +111,11 @@ static final class FlatMapSingleObserver<R> implements SingleObserver<R> { final AtomicReference<Disposable> parent; - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; - FlatMapSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super R> actual) { + FlatMapSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super R> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -125,12 +125,12 @@ public void onSubscribe(final Disposable d) { @Override public void onSuccess(final R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(final Throwable e) { - actual.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java index 3b0ca21850..37795729b8 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicReference; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; @@ -25,12 +24,11 @@ /** * Maps the success value of the source MaybeSource into a Single. + * <p>History: 2.0.2 - experimental * @param <T> the input value type * @param <R> the result value type - * - * @since 2.0.2 - experimental + * @since 2.1 */ -@Experimental public final class MaybeFlatMapSingleElement<T, R> extends Maybe<R> { final MaybeSource<T> source; @@ -43,8 +41,8 @@ public MaybeFlatMapSingleElement(MaybeSource<T> source, Function<? super T, ? ex } @Override - protected void subscribeActual(MaybeObserver<? super R> actual) { - source.subscribe(new FlatMapMaybeObserver<T, R>(actual, mapper)); + protected void subscribeActual(MaybeObserver<? super R> downstream) { + source.subscribe(new FlatMapMaybeObserver<T, R>(downstream, mapper)); } static final class FlatMapMaybeObserver<T, R> @@ -53,12 +51,12 @@ static final class FlatMapMaybeObserver<T, R> private static final long serialVersionUID = 4827726964688405508L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super T, ? extends SingleSource<? extends R>> mapper; FlatMapMaybeObserver(MaybeObserver<? super R> actual, Function<? super T, ? extends SingleSource<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -75,7 +73,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -91,17 +89,17 @@ public void onSuccess(T value) { return; } - ss.subscribe(new FlatMapSingleObserver<R>(this, actual)); + ss.subscribe(new FlatMapSingleObserver<R>(this, downstream)); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } @@ -109,11 +107,11 @@ static final class FlatMapSingleObserver<R> implements SingleObserver<R> { final AtomicReference<Disposable> parent; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; - FlatMapSingleObserver(AtomicReference<Disposable> parent, MaybeObserver<? super R> actual) { + FlatMapSingleObserver(AtomicReference<Disposable> parent, MaybeObserver<? super R> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -123,12 +121,12 @@ public void onSubscribe(final Disposable d) { @Override public void onSuccess(final R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(final Throwable e) { - actual.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java index bca244ac4d..6463ef270b 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java @@ -46,25 +46,24 @@ static final class FlatMapMaybeObserver<T, R> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = 4375739915521278546L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super T, ? extends MaybeSource<? extends R>> mapper; - Disposable d; + Disposable upstream; FlatMapMaybeObserver(MaybeObserver<? super R> actual, Function<? super T, ? extends MaybeSource<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void dispose() { DisposableHelper.dispose(this); - d.dispose(); + upstream.dispose(); } @Override @@ -74,10 +73,10 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -89,7 +88,7 @@ public void onSuccess(T value) { source = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null MaybeSource"); } catch (Exception ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } @@ -100,12 +99,12 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } final class InnerObserver implements MaybeObserver<R> { @@ -117,17 +116,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java index 183588f290..2753d992da 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java @@ -42,44 +42,44 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } static final class FromCompletableObserver<T> implements CompletableObserver, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable d; + Disposable upstream; - FromCompletableObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + FromCompletableObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java index 1dd41d38bc..7a206007aa 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java @@ -17,6 +17,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; /** * Waits until the source Future completes or the wait times out; treats a {@code null} @@ -50,17 +51,11 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } else { v = future.get(timeout, unit); } - } catch (InterruptedException ex) { - if (!d.isDisposed()) { - observer.onError(ex); - } - return; - } catch (ExecutionException ex) { - if (!d.isDisposed()) { - observer.onError(ex.getCause()); + } catch (Throwable ex) { + if (ex instanceof ExecutionException) { + ex = ex.getCause(); } - return; - } catch (TimeoutException ex) { + Exceptions.throwIfFatal(ex); if (!d.isDisposed()) { observer.onError(ex); } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java index 66dab6d1ab..b610a06a12 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java @@ -42,44 +42,44 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } static final class FromSingleObserver<T> implements SingleObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable d; + Disposable upstream; - FromSingleObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + FromSingleObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - actual.onSuccess(value); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java index 80454b81f8..805f7cdc0d 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java @@ -35,47 +35,47 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class HideMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable d; + Disposable upstream; - HideMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + HideMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java index 8b3c40e1f3..000de8cf0e 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java @@ -35,50 +35,50 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class IgnoreMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable d; + Disposable upstream; - IgnoreMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + IgnoreMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java index 10f6b028a3..ac49d66e04 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java @@ -44,50 +44,50 @@ public Maybe<T> fuseToMaybe() { static final class IgnoreMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final CompletableObserver actual; + final CompletableObserver downstream; - Disposable d; + Disposable upstream; - IgnoreMaybeObserver(CompletableObserver actual) { - this.actual = actual; + IgnoreMaybeObserver(CompletableObserver downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java index 284977787a..642dc641ce 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java @@ -37,46 +37,46 @@ protected void subscribeActual(MaybeObserver<? super Boolean> observer) { static final class IsEmptyMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super Boolean> actual; + final MaybeObserver<? super Boolean> downstream; - Disposable d; + Disposable upstream; - IsEmptyMaybeObserver(MaybeObserver<? super Boolean> actual) { - this.actual = actual; + IsEmptyMaybeObserver(MaybeObserver<? super Boolean> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(false); + downstream.onSuccess(false); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onSuccess(true); + downstream.onSuccess(true); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java index 84811071f5..c8acdff348 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java @@ -52,50 +52,50 @@ protected void subscribeActual(SingleObserver<? super Boolean> observer) { static final class IsEmptyMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; - Disposable d; + Disposable upstream; - IsEmptyMaybeObserver(SingleObserver<? super Boolean> actual) { - this.actual = actual; + IsEmptyMaybeObserver(SingleObserver<? super Boolean> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - actual.onSuccess(false); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(false); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onSuccess(true); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(true); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java index 578d9b319d..7d7c7a47b5 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java @@ -42,35 +42,35 @@ protected void subscribeActual(MaybeObserver<? super R> observer) { static final class MapMaybeObserver<T, R> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super T, ? extends R> mapper; - Disposable d; + Disposable upstream; MapMaybeObserver(MaybeObserver<? super R> actual, Function<? super T, ? extends R> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void dispose() { - Disposable d = this.d; - this.d = DisposableHelper.DISPOSED; + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; d.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -82,24 +82,21 @@ public void onSuccess(T value) { v = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null item"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(v); + downstream.onSuccess(v); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } - - } - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java new file mode 100644 index 0000000000..2b74829ba1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Maybe source into a single Notification of + * equal kind. + * + * @param <T> the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaybeMaterialize<T> extends Single<Notification<T>> { + + final Maybe<T> source; + + public MaybeMaterialize(Maybe<T> source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver<? super Notification<T>> observer) { + source.subscribe(new MaterializeSingleObserver<T>(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java index a012deecf8..1edfdd69da 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java @@ -72,7 +72,7 @@ static final class MergeMaybeObserver<T> private static final long serialVersionUID = -660395290758764731L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final CompositeDisposable set; @@ -91,7 +91,7 @@ static final class MergeMaybeObserver<T> long consumed; MergeMaybeObserver(Subscriber<? super T> actual, int sourceCount, SimpleQueueWithConsumerIndex<Object> queue) { - this.actual = actual; + this.downstream = actual; this.sourceCount = sourceCount; this.set = new CompositeDisposable(); this.requested = new AtomicLong(); @@ -184,7 +184,7 @@ boolean isCancelled() { @SuppressWarnings("unchecked") void drainNormal() { int missed = 1; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; SimpleQueueWithConsumerIndex<Object> q = queue; long e = consumed; @@ -252,7 +252,7 @@ void drainNormal() { void drainFused() { int missed = 1; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; SimpleQueueWithConsumerIndex<Object> q = queue; for (;;) { @@ -318,7 +318,6 @@ static final class MpscFillOnceSimpleQueue<T> extends AtomicReferenceArray<T> implements SimpleQueueWithConsumerIndex<T> { - private static final long serialVersionUID = -7969063454040569579L; final AtomicInteger producerIndex; diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java index 3feda9db3e..cce201fedb 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java @@ -42,10 +42,9 @@ static final class ObserveOnMaybeObserver<T> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable, Runnable { - private static final long serialVersionUID = 8571289934935992137L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Scheduler scheduler; @@ -53,7 +52,7 @@ static final class ObserveOnMaybeObserver<T> Throwable error; ObserveOnMaybeObserver(MaybeObserver<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @@ -70,7 +69,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -96,14 +95,14 @@ public void run() { Throwable ex = error; if (ex != null) { error = null; - actual.onError(ex); + downstream.onError(ex); } else { T v = value; if (v != null) { value = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java index 54825485d4..10fe0610bc 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java @@ -42,29 +42,29 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class OnErrorCompleteMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Predicate<? super Throwable> predicate; - Disposable d; + Disposable upstream; OnErrorCompleteMaybeObserver(MaybeObserver<? super T> actual, Predicate<? super Throwable> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -75,30 +75,30 @@ public void onError(Throwable e) { b = predicate.test(e); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } if (b) { - actual.onComplete(); + downstream.onComplete(); } else { - actual.onError(e); + downstream.onError(e); } } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java index ebd866c667..f7fff884ad 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java @@ -50,10 +50,9 @@ static final class OnErrorNextMaybeObserver<T> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = 2026620218879969836L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Function<? super Throwable, ? extends MaybeSource<? extends T>> resumeFunction; @@ -62,7 +61,7 @@ static final class OnErrorNextMaybeObserver<T> OnErrorNextMaybeObserver(MaybeObserver<? super T> actual, Function<? super Throwable, ? extends MaybeSource<? extends T>> resumeFunction, boolean allowFatal) { - this.actual = actual; + this.downstream = actual; this.resumeFunction = resumeFunction; this.allowFatal = allowFatal; } @@ -80,19 +79,19 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { if (!allowFatal && !(e instanceof Exception)) { - actual.onError(e); + downstream.onError(e); return; } MaybeSource<? extends T> m; @@ -101,48 +100,48 @@ public void onError(Throwable e) { m = ObjectHelper.requireNonNull(resumeFunction.apply(e), "The resumeFunction returned a null MaybeSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } DisposableHelper.replace(this, null); - m.subscribe(new NextMaybeObserver<T>(actual, this)); + m.subscribe(new NextMaybeObserver<T>(downstream, this)); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } static final class NextMaybeObserver<T> implements MaybeObserver<T> { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - final AtomicReference<Disposable> d; + final AtomicReference<Disposable> upstream; NextMaybeObserver(MaybeObserver<? super T> actual, AtomicReference<Disposable> d) { - this.actual = actual; - this.d = d; + this.downstream = actual; + this.upstream = d; } @Override public void onSubscribe(Disposable d) { - DisposableHelper.setOnce(this.d, d); + DisposableHelper.setOnce(this.upstream, d); } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java index dda373fdb8..568f15009a 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java @@ -41,40 +41,40 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class OnErrorReturnMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Function<? super Throwable, ? extends T> valueSupplier; - Disposable d; + Disposable upstream; OnErrorReturnMaybeObserver(MaybeObserver<? super T> actual, Function<? super Throwable, ? extends T> valueSupplier) { - this.actual = actual; + this.downstream = actual; this.valueSupplier = valueSupplier; } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -85,16 +85,16 @@ public void onError(Throwable e) { v = ObjectHelper.requireNonNull(valueSupplier.apply(e), "The valueSupplier returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } - actual.onSuccess(v); + downstream.onSuccess(v); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java index 46fcf24e01..1d6179abf4 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java @@ -57,14 +57,14 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { } static final class MaybePeekObserver<T> implements MaybeObserver<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final MaybePeek<T> parent; - Disposable d; + Disposable upstream; MaybePeekObserver(MaybeObserver<? super T> actual, MaybePeek<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } @@ -77,37 +77,37 @@ public void dispose() { RxJavaPlugins.onError(ex); } - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { + if (DisposableHelper.validate(this.upstream, d)) { try { parent.onSubscribeCall.accept(d); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); d.dispose(); - this.d = DisposableHelper.DISPOSED; - EmptyDisposable.error(ex, actual); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(ex, downstream); return; } - this.d = d; + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - if (this.d == DisposableHelper.DISPOSED) { + if (this.upstream == DisposableHelper.DISPOSED) { return; } try { @@ -117,16 +117,16 @@ public void onSuccess(T value) { onErrorInner(ex); return; } - this.d = DisposableHelper.DISPOSED; + this.upstream = DisposableHelper.DISPOSED; - actual.onSuccess(value); + downstream.onSuccess(value); onAfterTerminate(); } @Override public void onError(Throwable e) { - if (this.d == DisposableHelper.DISPOSED) { + if (this.upstream == DisposableHelper.DISPOSED) { RxJavaPlugins.onError(e); return; } @@ -142,16 +142,16 @@ void onErrorInner(Throwable e) { e = new CompositeException(e, ex); } - this.d = DisposableHelper.DISPOSED; + this.upstream = DisposableHelper.DISPOSED; - actual.onError(e); + downstream.onError(e); onAfterTerminate(); } @Override public void onComplete() { - if (this.d == DisposableHelper.DISPOSED) { + if (this.upstream == DisposableHelper.DISPOSED) { return; } @@ -162,9 +162,9 @@ public void onComplete() { onErrorInner(ex); return; } - this.d = DisposableHelper.DISPOSED; + this.upstream = DisposableHelper.DISPOSED; - actual.onComplete(); + downstream.onComplete(); onAfterTerminate(); } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java index 9a7da8dfa0..da2ba08070 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java @@ -63,10 +63,10 @@ static final class SubscribeOnMaybeObserver<T> private static final long serialVersionUID = 8571289934935992137L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - SubscribeOnMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + SubscribeOnMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; this.task = new SequentialDisposable(); } @@ -88,17 +88,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java index 396714eb7b..68d3db37d7 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java @@ -44,12 +44,12 @@ static final class SwitchIfEmptyMaybeObserver<T> private static final long serialVersionUID = -2223459372976438024L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final MaybeSource<? extends T> other; SwitchIfEmptyMaybeObserver(MaybeObserver<? super T> actual, MaybeSource<? extends T> other) { - this.actual = actual; + this.downstream = actual; this.other = other; } @@ -66,18 +66,18 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override @@ -85,35 +85,39 @@ public void onComplete() { Disposable d = get(); if (d != DisposableHelper.DISPOSED) { if (compareAndSet(d, null)) { - other.subscribe(new OtherMaybeObserver<T>(actual, this)); + other.subscribe(new OtherMaybeObserver<T>(downstream, this)); } } } static final class OtherMaybeObserver<T> implements MaybeObserver<T> { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final AtomicReference<Disposable> parent; OtherMaybeObserver(MaybeObserver<? super T> actual, AtomicReference<Disposable> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } + @Override public void onSubscribe(Disposable d) { DisposableHelper.setOnce(parent, d); } + @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } + @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } + @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java new file mode 100644 index 0000000000..bd94beb3ea --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Subscribes to the other source if the main source is empty. + * + * @param <T> the value type + */ +public final class MaybeSwitchIfEmptySingle<T> extends Single<T> implements HasUpstreamMaybeSource<T> { + + final MaybeSource<T> source; + final SingleSource<? extends T> other; + + public MaybeSwitchIfEmptySingle(MaybeSource<T> source, SingleSource<? extends T> other) { + this.source = source; + this.other = other; + } + + @Override + public MaybeSource<T> source() { + return source; + } + + @Override + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new SwitchIfEmptyMaybeObserver<T>(observer, other)); + } + + static final class SwitchIfEmptyMaybeObserver<T> + extends AtomicReference<Disposable> + implements MaybeObserver<T>, Disposable { + + private static final long serialVersionUID = 4603919676453758899L; + + final SingleObserver<? super T> downstream; + + final SingleSource<? extends T> other; + + SwitchIfEmptyMaybeObserver(SingleObserver<? super T> actual, SingleSource<? extends T> other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED) { + if (compareAndSet(d, null)) { + other.subscribe(new OtherSingleObserver<T>(downstream, this)); + } + } + } + + static final class OtherSingleObserver<T> implements SingleObserver<T> { + + final SingleObserver<? super T> downstream; + + final AtomicReference<Disposable> parent; + OtherSingleObserver(SingleObserver<? super T> actual, AtomicReference<Disposable> parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(parent, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java index 14f030e5a1..56a5cac4f7 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java @@ -51,12 +51,12 @@ static final class TakeUntilMainMaybeObserver<T, U> private static final long serialVersionUID = -2187421758664251153L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final TakeUntilOtherMaybeObserver<U> other; - TakeUntilMainMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + TakeUntilMainMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; this.other = new TakeUntilOtherMaybeObserver<U>(this); } @@ -80,7 +80,7 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onSuccess(value); + downstream.onSuccess(value); } } @@ -88,7 +88,7 @@ public void onSuccess(T value) { public void onError(Throwable e) { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -98,13 +98,13 @@ public void onError(Throwable e) { public void onComplete() { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onComplete(); + downstream.onComplete(); } } void otherError(Throwable e) { if (DisposableHelper.dispose(this)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -112,7 +112,7 @@ void otherError(Throwable e) { void otherComplete() { if (DisposableHelper.dispose(this)) { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java index 3ddd26d570..656cf473b5 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java @@ -54,12 +54,12 @@ static final class TakeUntilMainMaybeObserver<T, U> private static final long serialVersionUID = -2187421758664251153L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final TakeUntilOtherMaybeObserver<U> other; - TakeUntilMainMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + TakeUntilMainMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; this.other = new TakeUntilOtherMaybeObserver<U>(this); } @@ -83,7 +83,7 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onSuccess(value); + downstream.onSuccess(value); } } @@ -91,7 +91,7 @@ public void onSuccess(T value) { public void onError(Throwable e) { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -101,13 +101,13 @@ public void onError(Throwable e) { public void onComplete() { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onComplete(); + downstream.onComplete(); } } void otherError(Throwable e) { if (DisposableHelper.dispose(this)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -115,7 +115,7 @@ void otherError(Throwable e) { void otherComplete() { if (DisposableHelper.dispose(this)) { - actual.onComplete(); + downstream.onComplete(); } } @@ -132,13 +132,12 @@ static final class TakeUntilOtherMaybeObserver<U> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override public void onNext(Object value) { + SubscriptionHelper.cancel(this); parent.otherComplete(); } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java index 3e8a9ad050..83d22cf232 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java @@ -54,10 +54,9 @@ static final class TimeoutMainMaybeObserver<T, U> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = -5955289211445418871L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final TimeoutOtherMaybeObserver<T, U> other; @@ -66,7 +65,7 @@ static final class TimeoutMainMaybeObserver<T, U> final TimeoutFallbackMaybeObserver<T> otherObserver; TimeoutMainMaybeObserver(MaybeObserver<? super T> actual, MaybeSource<? extends T> fallback) { - this.actual = actual; + this.downstream = actual; this.other = new TimeoutOtherMaybeObserver<T, U>(this); this.fallback = fallback; this.otherObserver = fallback != null ? new TimeoutFallbackMaybeObserver<T>(actual) : null; @@ -96,7 +95,7 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onSuccess(value); + downstream.onSuccess(value); } } @@ -104,7 +103,7 @@ public void onSuccess(T value) { public void onError(Throwable e) { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -114,13 +113,13 @@ public void onError(Throwable e) { public void onComplete() { DisposableHelper.dispose(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onComplete(); + downstream.onComplete(); } } public void otherError(Throwable e) { if (DisposableHelper.dispose(this)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -129,7 +128,7 @@ public void otherError(Throwable e) { public void otherComplete() { if (DisposableHelper.dispose(this)) { if (fallback == null) { - actual.onError(new TimeoutException()); + downstream.onError(new TimeoutException()); } else { fallback.subscribe(otherObserver); } @@ -141,7 +140,6 @@ static final class TimeoutOtherMaybeObserver<T, U> extends AtomicReference<Disposable> implements MaybeObserver<Object> { - private static final long serialVersionUID = 8663801314800248617L; final TimeoutMainMaybeObserver<T, U> parent; @@ -174,13 +172,12 @@ static final class TimeoutFallbackMaybeObserver<T> extends AtomicReference<Disposable> implements MaybeObserver<T> { - private static final long serialVersionUID = 8663801314800248617L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - TimeoutFallbackMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + TimeoutFallbackMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override @@ -190,17 +187,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java index 79c2aea625..f0d690d567 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java @@ -57,10 +57,9 @@ static final class TimeoutMainMaybeObserver<T, U> extends AtomicReference<Disposable> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = -5955289211445418871L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final TimeoutOtherMaybeObserver<T, U> other; @@ -69,7 +68,7 @@ static final class TimeoutMainMaybeObserver<T, U> final TimeoutFallbackMaybeObserver<T> otherObserver; TimeoutMainMaybeObserver(MaybeObserver<? super T> actual, MaybeSource<? extends T> fallback) { - this.actual = actual; + this.downstream = actual; this.other = new TimeoutOtherMaybeObserver<T, U>(this); this.fallback = fallback; this.otherObserver = fallback != null ? new TimeoutFallbackMaybeObserver<T>(actual) : null; @@ -99,7 +98,7 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onSuccess(value); + downstream.onSuccess(value); } } @@ -107,7 +106,7 @@ public void onSuccess(T value) { public void onError(Throwable e) { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -117,13 +116,13 @@ public void onError(Throwable e) { public void onComplete() { SubscriptionHelper.cancel(other); if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { - actual.onComplete(); + downstream.onComplete(); } } public void otherError(Throwable e) { if (DisposableHelper.dispose(this)) { - actual.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -132,7 +131,7 @@ public void otherError(Throwable e) { public void otherComplete() { if (DisposableHelper.dispose(this)) { if (fallback == null) { - actual.onError(new TimeoutException()); + downstream.onError(new TimeoutException()); } else { fallback.subscribe(otherObserver); } @@ -144,7 +143,6 @@ static final class TimeoutOtherMaybeObserver<T, U> extends AtomicReference<Subscription> implements FlowableSubscriber<Object> { - private static final long serialVersionUID = 8663801314800248617L; final TimeoutMainMaybeObserver<T, U> parent; @@ -155,9 +153,7 @@ static final class TimeoutOtherMaybeObserver<T, U> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -181,13 +177,12 @@ static final class TimeoutFallbackMaybeObserver<T> extends AtomicReference<Disposable> implements MaybeObserver<T> { - private static final long serialVersionUID = 8663801314800248617L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - TimeoutFallbackMaybeObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + TimeoutFallbackMaybeObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override @@ -197,19 +192,17 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } - - } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java index 3aaacd98d3..5d2c1c4151 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java @@ -47,15 +47,15 @@ protected void subscribeActual(final MaybeObserver<? super Long> observer) { static final class TimerDisposable extends AtomicReference<Disposable> implements Disposable, Runnable { private static final long serialVersionUID = 2875964065294031672L; - final MaybeObserver<? super Long> actual; + final MaybeObserver<? super Long> downstream; - TimerDisposable(final MaybeObserver<? super Long> actual) { - this.actual = actual; + TimerDisposable(final MaybeObserver<? super Long> downstream) { + this.downstream = downstream; } @Override public void run() { - actual.onSuccess(0L); + downstream.onSuccess(0L); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java index 7c6fb1e7f8..c477a86b50 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java @@ -50,18 +50,18 @@ static final class MaybeToFlowableSubscriber<T> extends DeferredScalarSubscripti private static final long serialVersionUID = 7603343402964826922L; - Disposable d; + Disposable upstream; - MaybeToFlowableSubscriber(Subscriber<? super T> actual) { - super(actual); + MaybeToFlowableSubscriber(Subscriber<? super T> downstream) { + super(downstream); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -72,18 +72,18 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void cancel() { super.cancel(); - d.dispose(); + upstream.dispose(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java index dabd8a0d1b..8caa294718 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java @@ -39,27 +39,39 @@ public MaybeSource<T> source() { } @Override - protected void subscribeActual(Observer<? super T> s) { - source.subscribe(new MaybeToFlowableSubscriber<T>(s)); + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(create(observer)); } - static final class MaybeToFlowableSubscriber<T> extends DeferredScalarDisposable<T> + /** + * Creates a {@link MaybeObserver} wrapper around a {@link Observer}. + * <p>History: 2.1.11 - experimental + * @param <T> the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new MaybeObserver instance + * @since 2.2 + */ + public static <T> MaybeObserver<T> create(Observer<? super T> downstream) { + return new MaybeToObservableObserver<T>(downstream); + } + + static final class MaybeToObservableObserver<T> extends DeferredScalarDisposable<T> implements MaybeObserver<T> { private static final long serialVersionUID = 7603343402964826922L; - Disposable d; + Disposable upstream; - MaybeToFlowableSubscriber(Observer<? super T> actual) { - super(actual); + MaybeToObservableObserver(Observer<? super T> downstream) { + super(downstream); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -81,7 +93,7 @@ public void onComplete() { @Override public void dispose() { super.dispose(); - d.dispose(); + upstream.dispose(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java index a15eb0f167..146bbb23f8 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java @@ -47,55 +47,55 @@ protected void subscribeActual(SingleObserver<? super T> observer) { } static final class ToSingleMaybeSubscriber<T> implements MaybeObserver<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final T defaultValue; - Disposable d; + Disposable upstream; ToSingleMaybeSubscriber(SingleObserver<? super T> actual, T defaultValue) { - this.actual = actual; + this.downstream = actual; this.defaultValue = defaultValue; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; - actual.onSuccess(value); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (defaultValue != null) { - actual.onSuccess(defaultValue); + downstream.onSuccess(defaultValue); } else { - actual.onError(new NoSuchElementException("The MaybeSource is empty")); + downstream.onError(new NoSuchElementException("The MaybeSource is empty")); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java index 2375c01221..a6afe4c51a 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java @@ -43,14 +43,14 @@ static final class UnsubscribeOnMaybeObserver<T> extends AtomicReference<Disposa private static final long serialVersionUID = 3256698449646456986L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Scheduler scheduler; Disposable ds; UnsubscribeOnMaybeObserver(MaybeObserver<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @@ -76,23 +76,23 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java index 130043471f..4628d1261c 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java @@ -99,28 +99,27 @@ static final class UsingObserver<T, D> extends AtomicReference<Object> implements MaybeObserver<T>, Disposable { - private static final long serialVersionUID = -674404550052917487L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final Consumer<? super D> disposer; final boolean eager; - Disposable d; + Disposable upstream; UsingObserver(MaybeObserver<? super T> actual, D resource, Consumer<? super D> disposer, boolean eager) { super(resource); - this.actual = actual; + this.downstream = actual; this.disposer = disposer; this.eager = eager; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; disposeResourceAfter(); } @@ -139,22 +138,22 @@ void disposeResourceAfter() { @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @SuppressWarnings("unchecked") @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object resource = getAndSet(this); if (resource != this) { @@ -162,7 +161,7 @@ public void onSuccess(T value) { disposer.accept((D)resource); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } } else { @@ -170,7 +169,7 @@ public void onSuccess(T value) { } } - actual.onSuccess(value); + downstream.onSuccess(value); if (!eager) { disposeResourceAfter(); @@ -180,7 +179,7 @@ public void onSuccess(T value) { @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object resource = getAndSet(this); if (resource != this) { @@ -195,7 +194,7 @@ public void onError(Throwable e) { } } - actual.onError(e); + downstream.onError(e); if (!eager) { disposeResourceAfter(); @@ -205,7 +204,7 @@ public void onError(Throwable e) { @SuppressWarnings("unchecked") @Override public void onComplete() { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object resource = getAndSet(this); if (resource != this) { @@ -213,7 +212,7 @@ public void onComplete() { disposer.accept((D)resource); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } } else { @@ -221,7 +220,7 @@ public void onComplete() { } } - actual.onComplete(); + downstream.onComplete(); if (!eager) { disposeResourceAfter(); diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java index eeda31ac19..d9cc0028af 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java @@ -39,7 +39,6 @@ protected void subscribeActual(MaybeObserver<? super R> observer) { MaybeSource<? extends T>[] sources = this.sources; int n = sources.length; - if (n == 1) { sources[0].subscribe(new MaybeMap.MapMaybeObserver<T, R>(observer, new SingletonArrayFunc())); return; @@ -66,10 +65,9 @@ protected void subscribeActual(MaybeObserver<? super R> observer) { static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposable { - private static final long serialVersionUID = -5556924161382950569L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super Object[], ? extends R> zipper; @@ -80,7 +78,7 @@ static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposa @SuppressWarnings("unchecked") ZipCoordinator(MaybeObserver<? super R> observer, int n, Function<? super Object[], ? extends R> zipper) { super(n); - this.actual = observer; + this.downstream = observer; this.zipper = zipper; ZipMaybeObserver<T>[] o = new ZipMaybeObserver[n]; for (int i = 0; i < n; i++) { @@ -113,11 +111,11 @@ void innerSuccess(T value, int index) { v = ObjectHelper.requireNonNull(zipper.apply(values), "The zipper returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(v); + downstream.onSuccess(v); } } @@ -135,7 +133,7 @@ void disposeExcept(int index) { void innerError(Throwable ex, int index) { if (getAndSet(0) > 0) { disposeExcept(index); - actual.onError(ex); + downstream.onError(ex); } else { RxJavaPlugins.onError(ex); } @@ -144,7 +142,7 @@ void innerError(Throwable ex, int index) { void innerComplete(int index) { if (getAndSet(0) > 0) { disposeExcept(index); - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java new file mode 100644 index 0000000000..454c45b4c1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * After Completable completes, it relays the signals + * of the ObservableSource to the downstream observer. + * + * @param <R> the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class CompletableAndThenObservable<R> extends Observable<R> { + + final CompletableSource source; + + final ObservableSource<? extends R> other; + + public CompletableAndThenObservable(CompletableSource source, + ObservableSource<? extends R> other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + AndThenObservableObserver<R> parent = new AndThenObservableObserver<R>(observer, other); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class AndThenObservableObserver<R> + extends AtomicReference<Disposable> + implements Observer<R>, CompletableObserver, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer<? super R> downstream; + + ObservableSource<? extends R> other; + + AndThenObservableObserver(Observer<? super R> downstream, ObservableSource<? extends R> other) { + this.other = other; + this.downstream = downstream; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + ObservableSource<? extends R> o = other; + if (o == null) { + downstream.onComplete(); + } else { + other = null; + o.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java new file mode 100644 index 0000000000..70c397402e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * After Completable completes, it relays the signals + * of the Publisher to the downstream subscriber. + * + * @param <R> the result type of the Publisher and this operator + * @since 2.1.15 + */ +public final class CompletableAndThenPublisher<R> extends Flowable<R> { + + final CompletableSource source; + + final Publisher<? extends R> other; + + public CompletableAndThenPublisher(CompletableSource source, + Publisher<? extends R> other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new AndThenPublisherSubscriber<R>(s, other)); + } + + static final class AndThenPublisherSubscriber<R> + extends AtomicReference<Subscription> + implements FlowableSubscriber<R>, CompletableObserver, Subscription { + + private static final long serialVersionUID = -8948264376121066672L; + + final Subscriber<? super R> downstream; + + Publisher<? extends R> other; + + Disposable upstream; + + final AtomicLong requested; + + AndThenPublisherSubscriber(Subscriber<? super R> downstream, Publisher<? extends R> other) { + this.downstream = downstream; + this.other = other; + this.requested = new AtomicLong(); + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + Publisher<? extends R> p = other; + if (p == null) { + downstream.onComplete(); + } else { + other = null; + p.subscribe(this); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(this, requested, n); + } + + @Override + public void cancel() { + upstream.dispose(); + SubscriptionHelper.cancel(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this, requested, s); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java new file mode 100644 index 0000000000..249d01ef82 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java @@ -0,0 +1,290 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes or terminates (in error-delaying mode). + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @since 2.2 + */ +public final class FlowableConcatMapCompletable<T> extends Completable { + + final Flowable<T> source; + + final Function<? super T, ? extends CompletableSource> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapCompletable(Flowable<T> source, + Function<? super T, ? extends CompletableSource> mapper, + ErrorMode errorMode, + int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new ConcatMapCompletableObserver<T>(observer, mapper, errorMode, prefetch)); + } + + static final class ConcatMapCompletableObserver<T> + extends AtomicInteger + implements FlowableSubscriber<T>, Disposable { + + private static final long serialVersionUID = 3610901111000061034L; + + final CompletableObserver downstream; + + final Function<? super T, ? extends CompletableSource> mapper; + + final ErrorMode errorMode; + + final AtomicThrowable errors; + + final ConcatMapInnerObserver inner; + + final int prefetch; + + final SimplePlainQueue<T> queue; + + Subscription upstream; + + volatile boolean active; + + volatile boolean done; + + volatile boolean disposed; + + int consumed; + + ConcatMapCompletableObserver(CompletableObserver downstream, + Function<? super T, ? extends CompletableSource> mapper, + ErrorMode errorMode, int prefetch) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapInnerObserver(this); + this.queue = new SpscArrayQueue<T>(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (queue.offer(t)) { + drain(); + } else { + upstream.cancel(); + onError(new MissingBackpressureException("Queue full?!")); + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + t = errors.terminate(); + if (t != ExceptionHelper.TERMINATED) { + downstream.onError(t); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + done = true; + drain(); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + disposed = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode == ErrorMode.IMMEDIATE) { + upstream.cancel(); + ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + active = false; + drain(); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete() { + active = false; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + do { + if (disposed) { + queue.clear(); + return; + } + + if (!active) { + + if (errorMode == ErrorMode.BOUNDARY) { + if (errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (!empty) { + + int limit = prefetch - (prefetch >> 1); + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + queue.clear(); + upstream.cancel(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + active = true; + cs.subscribe(inner); + } + } + } while (decrementAndGet() != 0); + } + + static final class ConcatMapInnerObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = 5638352172918776687L; + + final ConcatMapCompletableObserver<?> parent; + + ConcatMapInnerObserver(ConcatMapCompletableObserver<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java new file mode 100644 index 0000000000..82a3009a6c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java @@ -0,0 +1,340 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link MaybeSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream element type + * @param <R> the output element type + * @since 2.2 + */ +public final class FlowableConcatMapMaybe<T, R> extends Flowable<R> { + + final Flowable<T> source; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapMaybe(Flowable<T> source, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new ConcatMapMaybeSubscriber<T, R>(s, mapper, prefetch, errorMode)); + } + + static final class ConcatMapMaybeSubscriber<T, R> + extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -9140123220065488293L; + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final int prefetch; + + final AtomicLong requested; + + final AtomicThrowable errors; + + final ConcatMapMaybeObserver<R> inner; + + final SimplePlainQueue<T> queue; + + final ErrorMode errorMode; + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + int consumed; + + R item; + + volatile int state; + + /** No inner MaybeSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner MaybeSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner MaybeSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapMaybeSubscriber(Subscriber<? super R> downstream, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.requested = new AtomicLong(); + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapMaybeObserver<R>(this); + this.queue = new SpscArrayQueue<T>(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("queue full?!")); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerComplete() { + this.state = STATE_INACTIVE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.cancel(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber<? super R> downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue<T> queue = this.queue; + AtomicThrowable errors = this.errors; + AtomicLong requested = this.requested; + int limit = prefetch - (prefetch >> 1); + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + MaybeSource<? extends R> ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ms.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + long e = emitted; + if (e != requested.get()) { + R w = item; + item = null; + + downstream.onNext(w); + + emitted = e + 1; + state = STATE_INACTIVE; + } else { + break; + } + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapMaybeObserver<R> + extends AtomicReference<Disposable> + implements MaybeObserver<R> { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapMaybeSubscriber<?, R> parent; + + ConcatMapMaybeObserver(ConcatMapMaybeSubscriber<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java new file mode 100644 index 0000000000..9be42fcb7b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java @@ -0,0 +1,330 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link SingleSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream element type + * @param <R> the output element type + * @since 2.2 + */ +public final class FlowableConcatMapSingle<T, R> extends Flowable<R> { + + final Flowable<T> source; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapSingle(Flowable<T> source, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new ConcatMapSingleSubscriber<T, R>(s, mapper, prefetch, errorMode)); + } + + static final class ConcatMapSingleSubscriber<T, R> + extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -9140123220065488293L; + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final int prefetch; + + final AtomicLong requested; + + final AtomicThrowable errors; + + final ConcatMapSingleObserver<R> inner; + + final SimplePlainQueue<T> queue; + + final ErrorMode errorMode; + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + int consumed; + + R item; + + volatile int state; + + /** No inner SingleSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner SingleSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner SingleSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapSingleSubscriber(Subscriber<? super R> downstream, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.requested = new AtomicLong(); + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapSingleObserver<R>(this); + this.queue = new SpscArrayQueue<T>(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("queue full?!")); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.cancel(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber<? super R> downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue<T> queue = this.queue; + AtomicThrowable errors = this.errors; + AtomicLong requested = this.requested; + int limit = prefetch - (prefetch >> 1); + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + SingleSource<? extends R> ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ss.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + long e = emitted; + if (e != requested.get()) { + R w = item; + item = null; + + downstream.onNext(w); + + emitted = e + 1; + state = STATE_INACTIVE; + } else { + break; + } + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapSingleObserver<R> + extends AtomicReference<Disposable> + implements SingleObserver<R> { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapSingleSubscriber<?, R> parent; + + ConcatMapSingleObserver(ConcatMapSingleSubscriber<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java new file mode 100644 index 0000000000..70294ff3d3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapCompletable<T> extends Completable { + + final Flowable<T> source; + + final Function<? super T, ? extends CompletableSource> mapper; + + final boolean delayErrors; + + public FlowableSwitchMapCompletable(Flowable<T> source, + Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new SwitchMapCompletableObserver<T>(observer, mapper, delayErrors)); + } + + static final class SwitchMapCompletableObserver<T> implements FlowableSubscriber<T>, Disposable { + + final CompletableObserver downstream; + + final Function<? super T, ? extends CompletableSource> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference<SwitchMapInnerObserver> inner; + + static final SwitchMapInnerObserver INNER_DISPOSED = new SwitchMapInnerObserver(null); + + volatile boolean done; + + Subscription upstream; + + SwitchMapCompletableObserver(CompletableObserver downstream, + Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference<SwitchMapInnerObserver>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + SwitchMapInnerObserver o = new SwitchMapInnerObserver(this); + + for (;;) { + SwitchMapInnerObserver current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, o)) { + if (current != null) { + current.dispose(); + } + c.subscribe(o); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (delayErrors) { + onComplete(); + } else { + disposeInner(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + if (inner.get() == null) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + + void disposeInner() { + SwitchMapInnerObserver o = inner.getAndSet(INNER_DISPOSED); + if (o != null && o != INNER_DISPOSED) { + o.dispose(); + } + } + + @Override + public void dispose() { + upstream.cancel(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return inner.get() == INNER_DISPOSED; + } + + void innerError(SwitchMapInnerObserver sender, Throwable error) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(error)) { + if (delayErrors) { + if (done) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + return; + } + } + RxJavaPlugins.onError(error); + } + + void innerComplete(SwitchMapInnerObserver sender) { + if (inner.compareAndSet(sender, null)) { + if (done) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + } + + static final class SwitchMapInnerObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = -8003404460084760287L; + + final SwitchMapCompletableObserver<?> parent; + + SwitchMapInnerObserver(SwitchMapCompletableObserver<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java new file mode 100644 index 0000000000..7bb3941d93 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @param <R> the downstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapMaybe<T, R> extends Flowable<R> { + + final Flowable<T> source; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final boolean delayErrors; + + public FlowableSwitchMapMaybe(Flowable<T> source, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new SwitchMapMaybeSubscriber<T, R>(s, mapper, delayErrors)); + } + + static final class SwitchMapMaybeSubscriber<T, R> extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -5402190102429853762L; + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicLong requested; + + final AtomicReference<SwitchMapMaybeObserver<R>> inner; + + static final SwitchMapMaybeObserver<Object> INNER_DISPOSED = + new SwitchMapMaybeObserver<Object>(null); + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + SwitchMapMaybeSubscriber(Subscriber<? super R> downstream, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.inner = new AtomicReference<SwitchMapMaybeObserver<R>>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapMaybeObserver<R> current = inner.get(); + if (current != null) { + current.dispose(); + } + + MaybeSource<? extends R> ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapMaybeObserver<R> observer = new SwitchMapMaybeObserver<R>(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ms.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapMaybeObserver<R> current = inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + disposeInner(); + } + + void innerError(SwitchMapMaybeObserver<R> sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.cancel(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void innerComplete(SwitchMapMaybeObserver<R> sender) { + if (inner.compareAndSet(sender, null)) { + drain(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber<? super R> downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference<SwitchMapMaybeObserver<R>> inner = this.inner; + AtomicLong requested = this.requested; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapMaybeObserver<R> current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null || emitted == requested.get()) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + + emitted++; + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapMaybeObserver<R> + extends AtomicReference<Disposable> implements MaybeObserver<R> { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapMaybeSubscriber<?, R> parent; + + volatile R item; + + SwitchMapMaybeObserver(SwitchMapMaybeSubscriber<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java new file mode 100644 index 0000000000..752ee852b9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java @@ -0,0 +1,290 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value, optionally delaying + * errors from the main source or the inner sources. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @param <R> the downstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapSingle<T, R> extends Flowable<R> { + + final Flowable<T> source; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final boolean delayErrors; + + public FlowableSwitchMapSingle(Flowable<T> source, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new SwitchMapSingleSubscriber<T, R>(s, mapper, delayErrors)); + } + + static final class SwitchMapSingleSubscriber<T, R> extends AtomicInteger + implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -5402190102429853762L; + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicLong requested; + + final AtomicReference<SwitchMapSingleObserver<R>> inner; + + static final SwitchMapSingleObserver<Object> INNER_DISPOSED = + new SwitchMapSingleObserver<Object>(null); + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + SwitchMapSingleSubscriber(Subscriber<? super R> downstream, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.inner = new AtomicReference<SwitchMapSingleObserver<R>>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapSingleObserver<R> current = inner.get(); + if (current != null) { + current.dispose(); + } + + SingleSource<? extends R> ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapSingleObserver<R> observer = new SwitchMapSingleObserver<R>(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ss.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapSingleObserver<R> current = inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + disposeInner(); + } + + void innerError(SwitchMapSingleObserver<R> sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.cancel(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber<? super R> downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference<SwitchMapSingleObserver<R>> inner = this.inner; + AtomicLong requested = this.requested; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapSingleObserver<R> current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null || emitted == requested.get()) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + + emitted++; + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapSingleObserver<R> + extends AtomicReference<Disposable> implements SingleObserver<R> { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapSingleSubscriber<?, R> parent; + + volatile R item; + + SwitchMapSingleObserver(SwitchMapSingleSubscriber<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java new file mode 100644 index 0000000000..ef8a87076d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A consumer that implements the consumer types of Maybe, Single and Completable + * and turns their signals into Notifications for a SingleObserver. + * @param <T> the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaterializeSingleObserver<T> +implements SingleObserver<T>, MaybeObserver<T>, CompletableObserver, Disposable { + + final SingleObserver<? super Notification<T>> downstream; + + Disposable upstream; + + public MaterializeSingleObserver(SingleObserver<? super Notification<T>> downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onComplete() { + downstream.onSuccess(Notification.<T>createOnComplete()); + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(Notification.<T>createOnNext(t)); + } + + @Override + public void onError(Throwable e) { + downstream.onSuccess(Notification.<T>createOnError(e)); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java new file mode 100644 index 0000000000..533e00addd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of a Maybe onto an ObservableSource and + * relays its signals to the downstream observer. + * + * @param <T> the success value type of the Maybe source + * @param <R> the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class MaybeFlatMapObservable<T, R> extends Observable<R> { + + final MaybeSource<T> source; + + final Function<? super T, ? extends ObservableSource<? extends R>> mapper; + + public MaybeFlatMapObservable(MaybeSource<T> source, + Function<? super T, ? extends ObservableSource<? extends R>> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + FlatMapObserver<T, R> parent = new FlatMapObserver<T, R>(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapObserver<T, R> + extends AtomicReference<Disposable> + implements Observer<R>, MaybeObserver<T>, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends ObservableSource<? extends R>> mapper; + + FlatMapObserver(Observer<? super R> downstream, Function<? super T, ? extends ObservableSource<? extends R>> mapper) { + this.downstream = downstream; + this.mapper = mapper; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T t) { + ObservableSource<? extends R> o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + o.subscribe(this); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java new file mode 100644 index 0000000000..2bd7b18f2e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Maps the success value of a Maybe onto a Publisher and + * relays its signals to the downstream subscriber. + * + * @param <T> the success value type of the Maybe source + * @param <R> the result type of the Publisher and this operator + * @since 2.1.15 + */ +public final class MaybeFlatMapPublisher<T, R> extends Flowable<R> { + + final MaybeSource<T> source; + + final Function<? super T, ? extends Publisher<? extends R>> mapper; + + public MaybeFlatMapPublisher(MaybeSource<T> source, + Function<? super T, ? extends Publisher<? extends R>> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber<? super R> s) { + source.subscribe(new FlatMapPublisherSubscriber<T, R>(s, mapper)); + } + + static final class FlatMapPublisherSubscriber<T, R> + extends AtomicReference<Subscription> + implements FlowableSubscriber<R>, MaybeObserver<T>, Subscription { + + private static final long serialVersionUID = -8948264376121066672L; + + final Subscriber<? super R> downstream; + + final Function<? super T, ? extends Publisher<? extends R>> mapper; + + Disposable upstream; + + final AtomicLong requested; + + FlatMapPublisherSubscriber(Subscriber<? super R> downstream, Function<? super T, ? extends Publisher<? extends R>> mapper) { + this.downstream = downstream; + this.mapper = mapper; + this.requested = new AtomicLong(); + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(this, requested, n); + } + + @Override + public void cancel() { + upstream.dispose(); + SubscriptionHelper.cancel(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + Publisher<? extends R> p; + + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + p.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this, requested, s); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java new file mode 100644 index 0000000000..41fa298128 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java @@ -0,0 +1,302 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes or terminates (in error-delaying mode). + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @since 2.2 + */ +public final class ObservableConcatMapCompletable<T> extends Completable { + + final Observable<T> source; + + final Function<? super T, ? extends CompletableSource> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapCompletable(Observable<T> source, + Function<? super T, ? extends CompletableSource> mapper, + ErrorMode errorMode, + int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, observer)) { + source.subscribe(new ConcatMapCompletableObserver<T>(observer, mapper, errorMode, prefetch)); + } + } + + static final class ConcatMapCompletableObserver<T> + extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = 3610901111000061034L; + + final CompletableObserver downstream; + + final Function<? super T, ? extends CompletableSource> mapper; + + final ErrorMode errorMode; + + final AtomicThrowable errors; + + final ConcatMapInnerObserver inner; + + final int prefetch; + + SimpleQueue<T> queue; + + Disposable upstream; + + volatile boolean active; + + volatile boolean done; + + volatile boolean disposed; + + ConcatMapCompletableObserver(CompletableObserver downstream, + Function<? super T, ? extends CompletableSource> mapper, + ErrorMode errorMode, int prefetch) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapInnerObserver(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable<T> qd = (QueueDisposable<T>) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueDisposable.SYNC) { + queue = qd; + done = true; + downstream.onSubscribe(this); + drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + queue = qd; + downstream.onSubscribe(this); + return; + } + } + queue = new SpscLinkedArrayQueue<T>(prefetch); + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (t != null) { + queue.offer(t); + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + disposed = true; + inner.dispose(); + t = errors.terminate(); + if (t != ExceptionHelper.TERMINATED) { + downstream.onError(t); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + done = true; + drain(); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + disposed = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode == ErrorMode.IMMEDIATE) { + disposed = true; + upstream.dispose(); + ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + active = false; + drain(); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete() { + active = false; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + AtomicThrowable errors = this.errors; + ErrorMode errorMode = this.errorMode; + + do { + if (disposed) { + queue.clear(); + return; + } + + if (!active) { + + if (errorMode == ErrorMode.BOUNDARY) { + if (errors.get() != null) { + disposed = true; + queue.clear(); + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + boolean empty = true; + CompletableSource cs = null; + try { + T v = queue.poll(); + if (v != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null CompletableSource"); + empty = false; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + disposed = true; + queue.clear(); + upstream.dispose(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + if (d && empty) { + disposed = true; + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (!empty) { + active = true; + cs.subscribe(inner); + } + } + } while (decrementAndGet() != 0); + } + + static final class ConcatMapInnerObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = 5638352172918776687L; + + final ConcatMapCompletableObserver<?> parent; + + ConcatMapInnerObserver(ConcatMapCompletableObserver<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java new file mode 100644 index 0000000000..32b174a19f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java @@ -0,0 +1,306 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link MaybeSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream element type + * @param <R> the output element type + * @since 2.2 + */ +public final class ObservableConcatMapMaybe<T, R> extends Observable<R> { + + final Observable<T> source; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapMaybe(Observable<T> source, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, observer)) { + source.subscribe(new ConcatMapMaybeMainObserver<T, R>(observer, mapper, prefetch, errorMode)); + } + } + + static final class ConcatMapMaybeMainObserver<T, R> + extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -9140123220065488293L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final AtomicThrowable errors; + + final ConcatMapMaybeObserver<R> inner; + + final SimplePlainQueue<T> queue; + + final ErrorMode errorMode; + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + R item; + + volatile int state; + + /** No inner MaybeSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner MaybeSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner MaybeSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapMaybeMainObserver(Observer<? super R> downstream, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapMaybeObserver<R>(this); + this.queue = new SpscLinkedArrayQueue<T>(prefetch); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerComplete() { + this.state = STATE_INACTIVE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.dispose(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer<? super R> downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue<T> queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + MaybeSource<? extends R> ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ms.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + R w = item; + item = null; + downstream.onNext(w); + + state = STATE_INACTIVE; + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapMaybeObserver<R> + extends AtomicReference<Disposable> + implements MaybeObserver<R> { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapMaybeMainObserver<?, R> parent; + + ConcatMapMaybeObserver(ConcatMapMaybeMainObserver<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java new file mode 100644 index 0000000000..1358e1ed9c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link SingleSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream element type + * @param <R> the output element type + * @since 2.2 + */ +public final class ObservableConcatMapSingle<T, R> extends Observable<R> { + + final Observable<T> source; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapSingle(Observable<T> source, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, observer)) { + source.subscribe(new ConcatMapSingleMainObserver<T, R>(observer, mapper, prefetch, errorMode)); + } + } + + static final class ConcatMapSingleMainObserver<T, R> + extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -9140123220065488293L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final AtomicThrowable errors; + + final ConcatMapSingleObserver<R> inner; + + final SimplePlainQueue<T> queue; + + final ErrorMode errorMode; + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + R item; + + volatile int state; + + /** No inner SingleSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner SingleSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner SingleSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapSingleMainObserver(Observer<? super R> downstream, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapSingleObserver<R>(this); + this.queue = new SpscLinkedArrayQueue<T>(prefetch); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.dispose(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer<? super R> downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue<T> queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + SingleSource<? extends R> ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ss.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + R w = item; + item = null; + downstream.onNext(w); + + state = STATE_INACTIVE; + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapSingleObserver<R> + extends AtomicReference<Disposable> + implements SingleObserver<R> { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapSingleMainObserver<?, R> parent; + + ConcatMapSingleObserver(ConcatMapSingleMainObserver<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java new file mode 100644 index 0000000000..1d4e8d247d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapCompletable<T> extends Completable { + + final Observable<T> source; + + final Function<? super T, ? extends CompletableSource> mapper; + + final boolean delayErrors; + + public ObservableSwitchMapCompletable(Observable<T> source, + Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, observer)) { + source.subscribe(new SwitchMapCompletableObserver<T>(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapCompletableObserver<T> implements Observer<T>, Disposable { + + final CompletableObserver downstream; + + final Function<? super T, ? extends CompletableSource> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference<SwitchMapInnerObserver> inner; + + static final SwitchMapInnerObserver INNER_DISPOSED = new SwitchMapInnerObserver(null); + + volatile boolean done; + + Disposable upstream; + + SwitchMapCompletableObserver(CompletableObserver downstream, + Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference<SwitchMapInnerObserver>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + SwitchMapInnerObserver o = new SwitchMapInnerObserver(this); + + for (;;) { + SwitchMapInnerObserver current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, o)) { + if (current != null) { + current.dispose(); + } + c.subscribe(o); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (delayErrors) { + onComplete(); + } else { + disposeInner(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + if (inner.get() == null) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + + void disposeInner() { + SwitchMapInnerObserver o = inner.getAndSet(INNER_DISPOSED); + if (o != null && o != INNER_DISPOSED) { + o.dispose(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return inner.get() == INNER_DISPOSED; + } + + void innerError(SwitchMapInnerObserver sender, Throwable error) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(error)) { + if (delayErrors) { + if (done) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + return; + } + } + RxJavaPlugins.onError(error); + } + + void innerComplete(SwitchMapInnerObserver sender) { + if (inner.compareAndSet(sender, null)) { + if (done) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + } + + static final class SwitchMapInnerObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = -8003404460084760287L; + + final SwitchMapCompletableObserver<?> parent; + + SwitchMapInnerObserver(SwitchMapCompletableObserver<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java new file mode 100644 index 0000000000..89086255e6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java @@ -0,0 +1,288 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @param <R> the downstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapMaybe<T, R> extends Observable<R> { + + final Observable<T> source; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final boolean delayErrors; + + public ObservableSwitchMapMaybe(Observable<T> source, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, observer)) { + source.subscribe(new SwitchMapMaybeMainObserver<T, R>(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapMaybeMainObserver<T, R> extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -5402190102429853762L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends MaybeSource<? extends R>> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference<SwitchMapMaybeObserver<R>> inner; + + static final SwitchMapMaybeObserver<Object> INNER_DISPOSED = + new SwitchMapMaybeObserver<Object>(null); + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + SwitchMapMaybeMainObserver(Observer<? super R> downstream, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference<SwitchMapMaybeObserver<R>>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapMaybeObserver<R> current = inner.get(); + if (current != null) { + current.dispose(); + } + + MaybeSource<? extends R> ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapMaybeObserver<R> observer = new SwitchMapMaybeObserver<R>(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ms.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapMaybeObserver<R> current = inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerError(SwitchMapMaybeObserver<R> sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.dispose(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void innerComplete(SwitchMapMaybeObserver<R> sender) { + if (inner.compareAndSet(sender, null)) { + drain(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer<? super R> downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference<SwitchMapMaybeObserver<R>> inner = this.inner; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapMaybeObserver<R> current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapMaybeObserver<R> + extends AtomicReference<Disposable> implements MaybeObserver<R> { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapMaybeMainObserver<?, R> parent; + + volatile R item; + + SwitchMapMaybeObserver(SwitchMapMaybeMainObserver<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java new file mode 100644 index 0000000000..f9871aa6f9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java @@ -0,0 +1,277 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + * <p>History: 2.1.11 - experimental + * @param <T> the upstream value type + * @param <R> the downstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapSingle<T, R> extends Observable<R> { + + final Observable<T> source; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final boolean delayErrors; + + public ObservableSwitchMapSingle(Observable<T> source, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, observer)) { + source.subscribe(new SwitchMapSingleMainObserver<T, R>(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapSingleMainObserver<T, R> extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -5402190102429853762L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends SingleSource<? extends R>> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference<SwitchMapSingleObserver<R>> inner; + + static final SwitchMapSingleObserver<Object> INNER_DISPOSED = + new SwitchMapSingleObserver<Object>(null); + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + SwitchMapSingleMainObserver(Observer<? super R> downstream, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference<SwitchMapSingleObserver<R>>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapSingleObserver<R> current = inner.get(); + if (current != null) { + current.dispose(); + } + + SingleSource<? extends R> ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapSingleObserver<R> observer = new SwitchMapSingleObserver<R>(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ss.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapSingleObserver<R> current = inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerError(SwitchMapSingleObserver<R> sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.dispose(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer<? super R> downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference<SwitchMapSingleObserver<R>> inner = this.inner; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapSingleObserver<R> current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapSingleObserver<R> + extends AtomicReference<Disposable> implements SingleObserver<R> { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapSingleMainObserver<?, R> parent; + + volatile R item; + + SwitchMapSingleObserver(SwitchMapSingleMainObserver<?, R> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java new file mode 100644 index 0000000000..2755a42c9d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.maybe.MaybeToObservable; +import io.reactivex.internal.operators.single.SingleToObservable; + +/** + * Utility class to extract a value from a scalar source reactive type, + * map it to a 0-1 type then subscribe the output type's consumer to it, + * saving on the overhead of the regular subscription channel. + * <p>History: 2.1.11 - experimental + * @since 2.2 + */ +final class ScalarXMapZHelper { + + private ScalarXMapZHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Try subscribing to a {@link CompletableSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param <T> the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link CompletableSource} + * @param observer the consumer to subscribe to the mapped {@link CompletableSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static <T> boolean tryAsCompletable(Object source, + Function<? super T, ? extends CompletableSource> mapper, + CompletableObserver observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable<T> call = (Callable<T>) source; + CompletableSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null CompletableSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(observer); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link MaybeSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param <T> the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link MaybeSource} + * @param observer the consumer to subscribe to the mapped {@link MaybeSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static <T, R> boolean tryAsMaybe(Object source, + Function<? super T, ? extends MaybeSource<? extends R>> mapper, + Observer<? super R> observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable<T> call = (Callable<T>) source; + MaybeSource<? extends R> cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null MaybeSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(MaybeToObservable.create(observer)); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link SingleSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param <T> the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link SingleSource} + * @param observer the consumer to subscribe to the mapped {@link SingleSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static <T, R> boolean tryAsSingle(Object source, + Function<? super T, ? extends SingleSource<? extends R>> mapper, + Observer<? super R> observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable<T> call = (Callable<T>) source; + SingleSource<? extends R> cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null SingleSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(SingleToObservable.create(observer)); + } + return true; + } + return false; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java new file mode 100644 index 0000000000..48d5793185 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of a Single onto an ObservableSource and + * relays its signals to the downstream observer. + * + * @param <T> the success value type of the Single source + * @param <R> the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class SingleFlatMapObservable<T, R> extends Observable<R> { + + final SingleSource<T> source; + + final Function<? super T, ? extends ObservableSource<? extends R>> mapper; + + public SingleFlatMapObservable(SingleSource<T> source, + Function<? super T, ? extends ObservableSource<? extends R>> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer<? super R> observer) { + FlatMapObserver<T, R> parent = new FlatMapObserver<T, R>(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapObserver<T, R> + extends AtomicReference<Disposable> + implements Observer<R>, SingleObserver<T>, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer<? super R> downstream; + + final Function<? super T, ? extends ObservableSource<? extends R>> mapper; + + FlatMapObserver(Observer<? super R> downstream, Function<? super T, ? extends ObservableSource<? extends R>> mapper) { + this.downstream = downstream; + this.mapper = mapper; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T t) { + ObservableSource<? extends R> o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + o.subscribe(this); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java index a9d2e92cb3..24a7cb7701 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java @@ -44,7 +44,6 @@ static final class BlockingObservableIterator<T> extends AtomicReference<Disposable> implements io.reactivex.Observer<T>, Iterator<T>, Disposable { - private static final long serialVersionUID = 6695226475494099826L; final SpscLinkedArrayQueue<T> queue; @@ -54,7 +53,7 @@ static final class BlockingObservableIterator<T> final Condition condition; volatile boolean done; - Throwable error; + volatile Throwable error; BlockingObservableIterator(int batchSize) { this.queue = new SpscLinkedArrayQueue<T>(batchSize); @@ -65,6 +64,13 @@ static final class BlockingObservableIterator<T> @Override public boolean hasNext() { for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } boolean d = done; boolean empty = queue.isEmpty(); if (d) { @@ -81,7 +87,7 @@ public boolean hasNext() { BlockingHelper.verifyNonBlocking(); lock.lock(); try { - while (!done && queue.isEmpty()) { + while (!done && queue.isEmpty() && !isDisposed()) { condition.await(); } } finally { @@ -107,8 +113,8 @@ public T next() { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override @@ -147,6 +153,7 @@ public void remove() { @Override public void dispose() { DisposableHelper.dispose(this); + signalConsumer(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java index 44156e1171..04940be5e6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; - import java.util.*; import io.reactivex.ObservableSource; @@ -43,10 +42,6 @@ public BlockingObservableMostRecent(ObservableSource<T> source, T initialValue) public Iterator<T> iterator() { MostRecentObserver<T> mostRecentObserver = new MostRecentObserver<T>(initialValue); - /** - * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain - * since it is for BlockingObservable. - */ source.subscribe(mostRecentObserver); return mostRecentObserver.getIterable(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java index 7fffa31214..2349ce4893 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java @@ -32,22 +32,23 @@ protected void subscribeActual(Observer<? super Boolean> t) { } static final class AllObserver<T> implements Observer<T>, Disposable { - final Observer<? super Boolean> actual; + final Observer<? super Boolean> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; AllObserver(Observer<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -61,15 +62,15 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (!b) { done = true; - s.dispose(); - actual.onNext(false); - actual.onComplete(); + upstream.dispose(); + downstream.onNext(false); + downstream.onComplete(); } } @@ -80,7 +81,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -89,18 +90,18 @@ public void onComplete() { return; } done = true; - actual.onNext(true); - actual.onComplete(); + downstream.onNext(true); + downstream.onComplete(); } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java index 59388fe651..1fb756233d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java @@ -40,22 +40,23 @@ public Observable<Boolean> fuseToObservable() { } static final class AllObserver<T> implements Observer<T>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; AllObserver(SingleObserver<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -69,14 +70,14 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (!b) { done = true; - s.dispose(); - actual.onSuccess(false); + upstream.dispose(); + downstream.onSuccess(false); } } @@ -87,7 +88,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -96,17 +97,17 @@ public void onComplete() { return; } done = true; - actual.onSuccess(true); + downstream.onSuccess(true); } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java index 55abe1499b..53068e7a91 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java @@ -32,15 +32,15 @@ public ObservableAmb(ObservableSource<? extends T>[] sources, Iterable<? extends @Override @SuppressWarnings("unchecked") - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { ObservableSource<? extends T>[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; try { for (ObservableSource<? extends T> p : sourcesIterable) { if (p == null) { - EmptyDisposable.error(new NullPointerException("One of the sources is null"), s); + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); return; } if (count == sources.length) { @@ -52,7 +52,7 @@ public void subscribeActual(Observer<? super T> s) { } } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } } else { @@ -60,27 +60,27 @@ public void subscribeActual(Observer<? super T> s) { } if (count == 0) { - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); return; } else if (count == 1) { - sources[0].subscribe(s); + sources[0].subscribe(observer); return; } - AmbCoordinator<T> ac = new AmbCoordinator<T>(s, count); + AmbCoordinator<T> ac = new AmbCoordinator<T>(observer, count); ac.subscribe(sources); } static final class AmbCoordinator<T> implements Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final AmbInnerObserver<T>[] observers; final AtomicInteger winner = new AtomicInteger(); @SuppressWarnings("unchecked") AmbCoordinator(Observer<? super T> actual, int count) { - this.actual = actual; + this.downstream = actual; this.observers = new AmbInnerObserver[count]; } @@ -88,10 +88,10 @@ public void subscribe(ObservableSource<? extends T>[] sources) { AmbInnerObserver<T>[] as = observers; int len = as.length; for (int i = 0; i < len; i++) { - as[i] = new AmbInnerObserver<T>(this, i + 1, actual); + as[i] = new AmbInnerObserver<T>(this, i + 1, downstream); } winner.lazySet(0); // release the contents of 'as' - actual.onSubscribe(this); + downstream.onSubscribe(this); for (int i = 0; i < len; i++) { if (winner.get() != 0) { @@ -142,29 +142,29 @@ static final class AmbInnerObserver<T> extends AtomicReference<Disposable> imple private static final long serialVersionUID = -1185974347409665484L; final AmbCoordinator<T> parent; final int index; - final Observer<? super T> actual; + final Observer<? super T> downstream; boolean won; - AmbInnerObserver(AmbCoordinator<T> parent, int index, Observer<? super T> actual) { + AmbInnerObserver(AmbCoordinator<T> parent, int index, Observer<? super T> downstream) { this.parent = parent; this.index = index; - this.actual = actual; + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override public void onNext(T t) { if (won) { - actual.onNext(t); + downstream.onNext(t); } else { if (parent.win(index)) { won = true; - actual.onNext(t); + downstream.onNext(t); } else { get().dispose(); } @@ -174,11 +174,11 @@ public void onNext(T t) { @Override public void onError(Throwable t) { if (won) { - actual.onError(t); + downstream.onError(t); } else { if (parent.win(index)) { won = true; - actual.onError(t); + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -188,11 +188,11 @@ public void onError(Throwable t) { @Override public void onComplete() { if (won) { - actual.onComplete(); + downstream.onComplete(); } else { if (parent.win(index)) { won = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java index a3c919067b..c1500c3fb6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java @@ -33,22 +33,23 @@ protected void subscribeActual(Observer<? super Boolean> t) { static final class AnyObserver<T> implements Observer<T>, Disposable { - final Observer<? super Boolean> actual; + final Observer<? super Boolean> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; AnyObserver(Observer<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -62,15 +63,15 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (b) { done = true; - s.dispose(); - actual.onNext(true); - actual.onComplete(); + upstream.dispose(); + downstream.onNext(true); + downstream.onComplete(); } } @@ -82,26 +83,26 @@ public void onError(Throwable t) { } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onNext(false); - actual.onComplete(); + downstream.onNext(false); + downstream.onComplete(); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java index 0b9c9db0fb..b8c7001ed5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java @@ -42,22 +42,23 @@ public Observable<Boolean> fuseToObservable() { static final class AnyObserver<T> implements Observer<T>, Disposable { - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; AnyObserver(SingleObserver<? super Boolean> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -71,14 +72,14 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (b) { done = true; - s.dispose(); - actual.onSuccess(true); + upstream.dispose(); + downstream.onSuccess(true); } } @@ -90,25 +91,25 @@ public void onError(Throwable t) { } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onSuccess(false); + downstream.onSuccess(false); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java index 4373a321aa..589b71c5f9 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java @@ -61,7 +61,7 @@ public static <T> void subscribe(ObservableSource<? extends T> o, Observer<? sup } } if (bs.isDisposed() - || o == BlockingObserver.TERMINATED + || v == BlockingObserver.TERMINATED || NotificationLite.acceptFull(v, observer)) { break; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java index a75c0ce07d..369eb4260d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java @@ -49,17 +49,17 @@ protected void subscribeActual(Observer<? super U> t) { } static final class BufferExactObserver<T, U extends Collection<? super T>> implements Observer<T>, Disposable { - final Observer<? super U> actual; + final Observer<? super U> downstream; final int count; final Callable<U> bufferSupplier; U buffer; int size; - Disposable s; + Disposable upstream; BufferExactObserver(Observer<? super U> actual, int count, Callable<U> bufferSupplier) { - this.actual = actual; + this.downstream = actual; this.count = count; this.bufferSupplier = bufferSupplier; } @@ -71,11 +71,11 @@ boolean createBuffer() { } catch (Throwable t) { Exceptions.throwIfFatal(t); buffer = null; - if (s == null) { - EmptyDisposable.error(t, actual); + if (upstream == null) { + EmptyDisposable.error(t, downstream); } else { - s.dispose(); - actual.onError(t); + upstream.dispose(); + downstream.onError(t); } return false; } @@ -86,21 +86,21 @@ boolean createBuffer() { } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override @@ -110,7 +110,7 @@ public void onNext(T t) { b.add(t); if (++size >= count) { - actual.onNext(b); + downstream.onNext(b); size = 0; createBuffer(); @@ -121,17 +121,19 @@ public void onNext(T t) { @Override public void onError(Throwable t) { buffer = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { U b = buffer; - buffer = null; - if (b != null && !b.isEmpty()) { - actual.onNext(b); + if (b != null) { + buffer = null; + if (!b.isEmpty()) { + downstream.onNext(b); + } + downstream.onComplete(); } - actual.onComplete(); } } @@ -139,19 +141,19 @@ static final class BufferSkipObserver<T, U extends Collection<? super T>> extends AtomicBoolean implements Observer<T>, Disposable { private static final long serialVersionUID = -8223395059921494546L; - final Observer<? super U> actual; + final Observer<? super U> downstream; final int count; final int skip; final Callable<U> bufferSupplier; - Disposable s; + Disposable upstream; final ArrayDeque<U> buffers; long index; BufferSkipObserver(Observer<? super U> actual, int count, int skip, Callable<U> bufferSupplier) { - this.actual = actual; + this.downstream = actual; this.count = count; this.skip = skip; this.bufferSupplier = bufferSupplier; @@ -159,22 +161,21 @@ static final class BufferSkipObserver<T, U extends Collection<? super T>> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override @@ -186,8 +187,8 @@ public void onNext(T t) { b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); } catch (Throwable e) { buffers.clear(); - s.dispose(); - actual.onError(e); + upstream.dispose(); + downstream.onError(e); return; } @@ -201,7 +202,7 @@ public void onNext(T t) { if (count <= b.size()) { it.remove(); - actual.onNext(b); + downstream.onNext(b); } } } @@ -209,15 +210,15 @@ public void onNext(T t) { @Override public void onError(Throwable t) { buffers.clear(); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { while (!buffers.isEmpty()) { - actual.onNext(buffers.poll()); + downstream.onNext(buffers.poll()); } - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java index a9051e474a..09f5b2d3d7 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java @@ -15,7 +15,7 @@ import java.util.*; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import io.reactivex.ObservableSource; import io.reactivex.Observer; @@ -24,11 +24,8 @@ import io.reactivex.functions.Function; import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.fuseable.SimplePlainQueue; -import io.reactivex.internal.observers.QueueDrainObserver; -import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.util.QueueDrainHelper; -import io.reactivex.observers.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; import io.reactivex.plugins.RxJavaPlugins; public final class ObservableBufferBoundary<T, U extends Collection<? super T>, Open, Close> @@ -47,55 +44,78 @@ public ObservableBufferBoundary(ObservableSource<T> source, ObservableSource<? e @Override protected void subscribeActual(Observer<? super U> t) { - source.subscribe(new BufferBoundaryObserver<T, U, Open, Close>( - new SerializedObserver<U>(t), - bufferOpen, bufferClose, bufferSupplier - )); + BufferBoundaryObserver<T, U, Open, Close> parent = + new BufferBoundaryObserver<T, U, Open, Close>( + t, bufferOpen, bufferClose, bufferSupplier + ); + t.onSubscribe(parent); + source.subscribe(parent); } - static final class BufferBoundaryObserver<T, U extends Collection<? super T>, Open, Close> - extends QueueDrainObserver<T, U, U> implements Disposable { + static final class BufferBoundaryObserver<T, C extends Collection<? super T>, Open, Close> + extends AtomicInteger implements Observer<T>, Disposable { + + private static final long serialVersionUID = -8466418554264089604L; + + final Observer<? super C> downstream; + + final Callable<C> bufferSupplier; + final ObservableSource<? extends Open> bufferOpen; + final Function<? super Open, ? extends ObservableSource<? extends Close>> bufferClose; - final Callable<U> bufferSupplier; - final CompositeDisposable resources; - Disposable s; + final CompositeDisposable observers; + + final AtomicReference<Disposable> upstream; - final List<U> buffers; + final AtomicThrowable errors; - final AtomicInteger windows = new AtomicInteger(); + volatile boolean done; - BufferBoundaryObserver(Observer<? super U> actual, + final SpscLinkedArrayQueue<C> queue; + + volatile boolean cancelled; + + long index; + + Map<Long, C> buffers; + + BufferBoundaryObserver(Observer<? super C> actual, ObservableSource<? extends Open> bufferOpen, Function<? super Open, ? extends ObservableSource<? extends Close>> bufferClose, - Callable<U> bufferSupplier) { - super(actual, new MpscLinkedQueue<U>()); + Callable<C> bufferSupplier + ) { + this.downstream = actual; + this.bufferSupplier = bufferSupplier; this.bufferOpen = bufferOpen; this.bufferClose = bufferClose; - this.bufferSupplier = bufferSupplier; - this.buffers = new LinkedList<U>(); - this.resources = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue<C>(bufferSize()); + this.observers = new CompositeDisposable(); + this.upstream = new AtomicReference<Disposable>(); + this.buffers = new LinkedHashMap<Long, C>(); + this.errors = new AtomicThrowable(); } - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - BufferOpenObserver<T, U, Open, Close> bos = new BufferOpenObserver<T, U, Open, Close>(this); - resources.add(bos); + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this.upstream, d)) { - actual.onSubscribe(this); + BufferOpenObserver<Open> open = new BufferOpenObserver<Open>(this); + observers.add(open); - windows.lazySet(1); - bufferOpen.subscribe(bos); + bufferOpen.subscribe(open); } } @Override public void onNext(T t) { synchronized (this) { - for (U b : buffers) { + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { b.add(t); } } @@ -103,194 +123,265 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - dispose(); - cancelled = true; - synchronized (this) { - buffers.clear(); + if (errors.addThrowable(t)) { + observers.dispose(); + synchronized (this) { + buffers = null; + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); } - actual.onError(t); } @Override public void onComplete() { - if (windows.decrementAndGet() == 0) { - complete(); - } - } - - void complete() { - List<U> list; + observers.dispose(); synchronized (this) { - list = new ArrayList<U>(buffers); - buffers.clear(); - } - - SimplePlainQueue<U> q = queue; - for (U u : list) { - q.offer(u); + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + queue.offer(b); + } + buffers = null; } done = true; - if (enter()) { - QueueDrainHelper.drainLoop(q, actual, false, this, this); - } + drain(); } @Override public void dispose() { - if (!cancelled) { + if (DisposableHelper.dispose(upstream)) { cancelled = true; - resources.dispose(); + observers.dispose(); + synchronized (this) { + buffers = null; + } + if (getAndIncrement() != 0) { + queue.clear(); + } } } - @Override public boolean isDisposed() { - return cancelled; - } - @Override - public void accept(Observer<? super U> a, U v) { - a.onNext(v); + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); } - void open(Open window) { - if (cancelled) { + void open(Open token) { + ObservableSource<? extends Close> p; + C buf; + try { + buf = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null Collection"); + p = ObjectHelper.requireNonNull(bufferClose.apply(token), "The bufferClose returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + DisposableHelper.dispose(upstream); + onError(ex); return; } - U b; - - try { - b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - onError(e); - return; + long idx = index; + index = idx + 1; + synchronized (this) { + Map<Long, C> bufs = buffers; + if (bufs == null) { + return; + } + bufs.put(idx, buf); } - ObservableSource<? extends Close> p; + BufferCloseObserver<T, C> bc = new BufferCloseObserver<T, C>(this, idx); + observers.add(bc); + p.subscribe(bc); + } - try { - p = ObjectHelper.requireNonNull(bufferClose.apply(window), "The buffer closing Observable is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - onError(e); - return; + void openComplete(BufferOpenObserver<Open> os) { + observers.delete(os); + if (observers.size() == 0) { + DisposableHelper.dispose(upstream); + done = true; + drain(); } + } - if (cancelled) { - return; + void close(BufferCloseObserver<T, C> closer, long idx) { + observers.delete(closer); + boolean makeDone = false; + if (observers.size() == 0) { + makeDone = true; + DisposableHelper.dispose(upstream); } - synchronized (this) { - if (cancelled) { + Map<Long, C> bufs = buffers; + if (bufs == null) { return; } - buffers.add(b); + queue.offer(buffers.remove(idx)); } + if (makeDone) { + done = true; + } + drain(); + } - BufferCloseObserver<T, U, Open, Close> bcs = new BufferCloseObserver<T, U, Open, Close>(b, this); - resources.add(bcs); + void boundaryError(Disposable observer, Throwable ex) { + DisposableHelper.dispose(upstream); + observers.delete(observer); + onError(ex); + } - windows.getAndIncrement(); + void drain() { + if (getAndIncrement() != 0) { + return; + } - p.subscribe(bcs); - } + int missed = 1; + Observer<? super C> a = downstream; + SpscLinkedArrayQueue<C> q = queue; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d && errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } + + C v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } - void openFinished(Disposable d) { - if (resources.remove(d)) { - if (windows.decrementAndGet() == 0) { - complete(); + missed = addAndGet(-missed); + if (missed == 0) { + break; } } } - void close(U b, Disposable d) { + static final class BufferOpenObserver<Open> + extends AtomicReference<Disposable> + implements Observer<Open>, Disposable { - boolean e; - synchronized (this) { - e = buffers.remove(b); + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundaryObserver<?, ?, Open, ?> parent; + + BufferOpenObserver(BufferBoundaryObserver<?, ?, Open, ?> parent) { + this.parent = parent; } - if (e) { - fastPathOrderedEmit(b, false, this); + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } - if (resources.remove(d)) { - if (windows.decrementAndGet() == 0) { - complete(); - } + @Override + public void onNext(Open t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + lazySet(DisposableHelper.DISPOSED); + parent.boundaryError(this, t); + } + + @Override + public void onComplete() { + lazySet(DisposableHelper.DISPOSED); + parent.openComplete(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; } } } - static final class BufferOpenObserver<T, U extends Collection<? super T>, Open, Close> - extends DisposableObserver<Open> { - final BufferBoundaryObserver<T, U, Open, Close> parent; + static final class BufferCloseObserver<T, C extends Collection<? super T>> + extends AtomicReference<Disposable> + implements Observer<Object>, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundaryObserver<T, C, ?, ?> parent; - boolean done; + final long index; - BufferOpenObserver(BufferBoundaryObserver<T, U, Open, Close> parent) { + BufferCloseObserver(BufferBoundaryObserver<T, C, ?, ?> parent, long index) { this.parent = parent; + this.index = index; } + @Override - public void onNext(Open t) { - if (done) { - return; + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + Disposable upstream = get(); + if (upstream != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + upstream.dispose(); + parent.close(this, index); } - parent.open(t); } @Override public void onError(Throwable t) { - if (done) { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.boundaryError(this, t); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - parent.onError(t); } @Override public void onComplete() { - if (done) { - return; + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.close(this, index); } - done = true; - parent.openFinished(this); - } - } - - static final class BufferCloseObserver<T, U extends Collection<? super T>, Open, Close> - extends DisposableObserver<Close> { - final BufferBoundaryObserver<T, U, Open, Close> parent; - final U value; - boolean done; - BufferCloseObserver(U value, BufferBoundaryObserver<T, U, Open, Close> parent) { - this.parent = parent; - this.value = value; } @Override - public void onNext(Close t) { - onComplete(); - } - - @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - parent.onError(t); + public void dispose() { + DisposableHelper.dispose(this); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - parent.close(value, this); + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java index ce97d264fc..b642bf1c9c 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java @@ -50,7 +50,7 @@ static final class BufferBoundarySupplierObserver<T, U extends Collection<? supe final Callable<U> bufferSupplier; final Callable<? extends ObservableSource<B>> boundarySupplier; - Disposable s; + Disposable upstream; final AtomicReference<Disposable> other = new AtomicReference<Disposable>(); @@ -64,11 +64,11 @@ static final class BufferBoundarySupplierObserver<T, U extends Collection<? supe } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - Observer<? super U> actual = this.actual; + Observer<? super U> actual = this.downstream; U b; @@ -77,7 +77,7 @@ public void onSubscribe(Disposable s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancelled = true; - s.dispose(); + d.dispose(); EmptyDisposable.error(e, actual); return; } @@ -91,7 +91,7 @@ public void onSubscribe(Disposable s) { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancelled = true; - s.dispose(); + d.dispose(); EmptyDisposable.error(ex, actual); return; } @@ -121,7 +121,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { dispose(); - actual.onError(t); + downstream.onError(t); } @Override @@ -137,7 +137,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainLoop(queue, actual, false, this, this); + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); } } @@ -145,7 +145,7 @@ public void onComplete() { public void dispose() { if (!cancelled) { cancelled = true; - s.dispose(); + upstream.dispose(); disposeOther(); if (enter()) { @@ -172,7 +172,7 @@ void next() { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - actual.onError(e); + downstream.onError(e); return; } @@ -183,36 +183,32 @@ void next() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancelled = true; - s.dispose(); - actual.onError(ex); + upstream.dispose(); + downstream.onError(ex); return; } BufferBoundaryObserver<T, U, B> bs = new BufferBoundaryObserver<T, U, B>(this); - Disposable o = other.get(); - - if (!other.compareAndSet(o, bs)) { - return; - } - - U b; - synchronized (this) { - b = buffer; - if (b == null) { - return; + if (DisposableHelper.replace(other, bs)) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; } - buffer = next; - } - boundary.subscribe(bs); + boundary.subscribe(bs); - fastPathEmit(b, false, this); + fastPathEmit(b, false, this); + } } @Override public void accept(Observer<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java index 9547e21195..b80d303be2 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java @@ -48,7 +48,7 @@ static final class BufferExactBoundaryObserver<T, U extends Collection<? super T final Callable<U> bufferSupplier; final ObservableSource<B> boundary; - Disposable s; + Disposable upstream; Disposable other; @@ -62,9 +62,9 @@ static final class BufferExactBoundaryObserver<T, U extends Collection<? super T } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; U b; @@ -73,8 +73,8 @@ public void onSubscribe(Disposable s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); cancelled = true; - s.dispose(); - EmptyDisposable.error(e, actual); + d.dispose(); + EmptyDisposable.error(e, downstream); return; } @@ -83,7 +83,7 @@ public void onSubscribe(Disposable s) { BufferBoundaryObserver<T, U, B> bs = new BufferBoundaryObserver<T, U, B>(this); other = bs; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (!cancelled) { boundary.subscribe(bs); @@ -105,7 +105,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { dispose(); - actual.onError(t); + downstream.onError(t); } @Override @@ -121,7 +121,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainLoop(queue, actual, false, this, this); + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); } } @@ -130,7 +130,7 @@ public void dispose() { if (!cancelled) { cancelled = true; other.dispose(); - s.dispose(); + upstream.dispose(); if (enter()) { queue.clear(); @@ -152,7 +152,7 @@ void next() { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - actual.onError(e); + downstream.onError(e); return; } @@ -170,7 +170,7 @@ void next() { @Override public void accept(Observer<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java index d521ebae80..b9f692db9d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java @@ -85,7 +85,7 @@ static final class BufferExactUnboundedObserver<T, U extends Collection<? super final TimeUnit unit; final Scheduler scheduler; - Disposable s; + Disposable upstream; U buffer; @@ -102,9 +102,9 @@ static final class BufferExactUnboundedObserver<T, U extends Collection<? super } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; U b; @@ -113,18 +113,18 @@ public void onSubscribe(Disposable s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - EmptyDisposable.error(e, actual); + EmptyDisposable.error(e, downstream); return; } buffer = b; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (!cancelled) { - Disposable d = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); - if (!timer.compareAndSet(null, d)) { - d.dispose(); + Disposable task = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); + if (!timer.compareAndSet(null, task)) { + task.dispose(); } } } @@ -146,7 +146,7 @@ public void onError(Throwable t) { synchronized (this) { buffer = null; } - actual.onError(t); + downstream.onError(t); DisposableHelper.dispose(timer); } @@ -161,7 +161,7 @@ public void onComplete() { queue.offer(b); done = true; if (enter()) { - QueueDrainHelper.drainLoop(queue, actual, false, this, this); + QueueDrainHelper.drainLoop(queue, downstream, false, null, this); } } DisposableHelper.dispose(timer); @@ -170,7 +170,7 @@ public void onComplete() { @Override public void dispose() { DisposableHelper.dispose(timer); - s.dispose(); + upstream.dispose(); } @Override @@ -186,7 +186,7 @@ public void run() { next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); dispose(); return; } @@ -210,7 +210,7 @@ public void run() { @Override public void accept(Observer<? super U> a, U v) { - actual.onNext(v); + downstream.onNext(v); } } @@ -223,8 +223,7 @@ static final class BufferSkipBoundedObserver<T, U extends Collection<? super T>> final Worker w; final List<U> buffers; - - Disposable s; + Disposable upstream; BufferSkipBoundedObserver(Observer<? super U> actual, Callable<U> bufferSupplier, long timespan, @@ -239,9 +238,9 @@ static final class BufferSkipBoundedObserver<T, U extends Collection<? super T>> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; final U b; // NOPMD @@ -249,15 +248,15 @@ public void onSubscribe(Disposable s) { b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); - EmptyDisposable.error(e, actual); + d.dispose(); + EmptyDisposable.error(e, downstream); w.dispose(); return; } buffers.add(b); - actual.onSubscribe(this); + downstream.onSubscribe(this); w.schedulePeriodically(this, timeskip, timeskip, unit); @@ -278,7 +277,7 @@ public void onNext(T t) { public void onError(Throwable t) { done = true; clear(); - actual.onError(t); + downstream.onError(t); w.dispose(); } @@ -295,7 +294,7 @@ public void onComplete() { } done = true; if (enter()) { - QueueDrainHelper.drainLoop(queue, actual, false, w, this); + QueueDrainHelper.drainLoop(queue, downstream, false, w, this); } } @@ -304,7 +303,7 @@ public void dispose() { if (!cancelled) { cancelled = true; clear(); - s.dispose(); + upstream.dispose(); w.dispose(); } } @@ -331,7 +330,7 @@ public void run() { b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); dispose(); return; } @@ -399,7 +398,7 @@ static final class BufferExactBoundedObserver<T, U extends Collection<? super T> Disposable timer; - Disposable s; + Disposable upstream; long producerIndex; @@ -420,9 +419,9 @@ static final class BufferExactBoundedObserver<T, U extends Collection<? super T> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; U b; @@ -430,15 +429,15 @@ public void onSubscribe(Disposable s) { b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); - EmptyDisposable.error(e, actual); + d.dispose(); + EmptyDisposable.error(e, downstream); w.dispose(); return; } buffer = b; - actual.onSubscribe(this); + downstream.onSubscribe(this); timer = w.schedulePeriodically(this, timespan, timespan, unit); } @@ -472,7 +471,7 @@ public void onNext(T t) { b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); dispose(); return; } @@ -491,7 +490,7 @@ public void onError(Throwable t) { synchronized (this) { buffer = null; } - actual.onError(t); + downstream.onError(t); w.dispose(); } @@ -505,10 +504,12 @@ public void onComplete() { buffer = null; } - queue.offer(b); - done = true; - if (enter()) { - QueueDrainHelper.drainLoop(queue, actual, false, this, this); + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + } } } @@ -517,12 +518,11 @@ public void accept(Observer<? super U> a, U v) { a.onNext(v); } - @Override public void dispose() { if (!cancelled) { cancelled = true; - s.dispose(); + upstream.dispose(); w.dispose(); synchronized (this) { buffer = null; @@ -544,7 +544,7 @@ public void run() { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - actual.onError(e); + downstream.onError(e); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java index b623bf2182..fdc3477b75 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java @@ -17,10 +17,6 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.SequentialDisposable; -import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.util.*; -import io.reactivex.plugins.RxJavaPlugins; /** * An observable which auto-connects to another observable, caches the elements @@ -28,61 +24,94 @@ * * @param <T> the source element type */ -public final class ObservableCache<T> extends AbstractObservableWithUpstream<T, T> { - /** The cache and replay state. */ - final CacheState<T> state; +public final class ObservableCache<T> extends AbstractObservableWithUpstream<T, T> +implements Observer<T> { + /** + * The subscription to the source should happen at most once. + */ final AtomicBoolean once; /** - * Creates a cached Observable with a default capacity hint of 16. - * @param <T> the value type - * @param source the source Observable to cache - * @return the CachedObservable instance + * The number of items per cached nodes. */ - public static <T> Observable<T> from(Observable<T> source) { - return from(source, 16); - } + final int capacityHint; /** - * Creates a cached Observable with the given capacity hint. - * @param <T> the value type - * @param source the source Observable to cache - * @param capacityHint the hint for the internal buffer size - * @return the CachedObservable instance + * The current known array of observer state to notify. */ - public static <T> Observable<T> from(Observable<T> source, int capacityHint) { - ObjectHelper.verifyPositive(capacityHint, "capacityHint"); - CacheState<T> state = new CacheState<T>(source, capacityHint); - return RxJavaPlugins.onAssembly(new ObservableCache<T>(source, state)); - } + final AtomicReference<CacheDisposable<T>[]> observers; /** - * Private constructor because state needs to be shared between the Observable body and - * the onSubscribe function. - * @param source the source Observable to cache - * @param state the cache state object + * A shared instance of an empty array of observers to avoid creating + * a new empty array when all observers dispose. + */ + @SuppressWarnings("rawtypes") + static final CacheDisposable[] EMPTY = new CacheDisposable[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember observers anymore. */ - private ObservableCache(Observable<T> source, CacheState<T> state) { + @SuppressWarnings("rawtypes") + static final CacheDisposable[] TERMINATED = new CacheDisposable[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node<T> head; + + /** + * The current tail of the linked structure holding the items. + */ + Node<T> tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #observers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming observer + * @param capacityHint the number of items expected (reduce allocation frequency) + */ + @SuppressWarnings("unchecked") + public ObservableCache(Observable<T> source, int capacityHint) { super(source); - this.state = state; + this.capacityHint = capacityHint; this.once = new AtomicBoolean(); + Node<T> n = new Node<T>(capacityHint); + this.head = n; + this.tail = n; + this.observers = new AtomicReference<CacheDisposable<T>[]>(EMPTY); } @Override protected void subscribeActual(Observer<? super T> t) { - // we can connect first because we replay everything anyway - ReplayDisposable<T> rp = new ReplayDisposable<T>(t, state); - t.onSubscribe(rp); - - state.addChild(rp); + CacheDisposable<T> consumer = new CacheDisposable<T>(t, this); + t.onSubscribe(consumer); + add(consumer); - // we ensure a single connection here to save an instance field of AtomicBoolean in state. if (!once.get() && once.compareAndSet(false, true)) { - state.connect(); + source.subscribe(this); + } else { + replay(consumer); } - - rp.replay(); } /** @@ -90,286 +119,281 @@ protected void subscribeActual(Observer<? super T> t) { * @return true if already connected */ /* public */boolean isConnected() { - return state.isConnected; + return once.get(); } /** * Returns true if there are observers subscribed to this observable. - * @return true if the cache has downstream Observers + * @return true if the cache has observers */ /* public */ boolean hasObservers() { - return state.observers.get().length != 0; + return observers.get().length != 0; } /** * Returns the number of events currently cached. - * @return the current number of elements in the cache + * @return the number of currently cached event count */ - /* public */ int cachedEventCount() { - return state.size(); + /* public */ long cachedEventCount() { + return size; } /** - * Contains the active child observers and the values to replay. - * - * @param <T> + * Atomically adds the consumer to the {@link #observers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add */ - static final class CacheState<T> extends LinkedArrayList implements Observer<T> { - /** The source observable to connect to. */ - final Observable<? extends T> source; - /** Holds onto the subscriber connected to source. */ - final SequentialDisposable connection; - /** Guarded by connection (not this). */ - final AtomicReference<ReplayDisposable<T>[]> observers; - /** The default empty array of observers. */ - @SuppressWarnings("rawtypes") - static final ReplayDisposable[] EMPTY = new ReplayDisposable[0]; - /** The default empty array of observers. */ - @SuppressWarnings("rawtypes") - static final ReplayDisposable[] TERMINATED = new ReplayDisposable[0]; - - /** Set to true after connection. */ - volatile boolean isConnected; - /** - * Indicates that the source has completed emitting values or the - * Observable was forcefully terminated. - */ - boolean sourceDone; + void add(CacheDisposable<T> consumer) { + for (;;) { + CacheDisposable<T>[] current = observers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; - @SuppressWarnings("unchecked") - CacheState(Observable<? extends T> source, int capacityHint) { - super(capacityHint); - this.source = source; - this.observers = new AtomicReference<ReplayDisposable<T>[]>(EMPTY); - this.connection = new SequentialDisposable(); + @SuppressWarnings("unchecked") + CacheDisposable<T>[] next = new CacheDisposable[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (observers.compareAndSet(current, next)) { + return; + } } - /** - * Adds a ReplayDisposable to the observers array atomically. - * @param p the target ReplayDisposable wrapping a downstream Observer with additional state - * @return true if the disposable was added, false otherwise - */ - public boolean addChild(ReplayDisposable<T> p) { - // guarding by connection to save on allocating another object - // thus there are two distinct locks guarding the value-addition and child come-and-go - for (;;) { - ReplayDisposable<T>[] a = observers.get(); - if (a == TERMINATED) { - return false; - } - int n = a.length; - - @SuppressWarnings("unchecked") - ReplayDisposable<T>[] b = new ReplayDisposable[n + 1]; - System.arraycopy(a, 0, b, 0, n); - b[n] = p; - if (observers.compareAndSet(a, b)) { - return true; + } + + /** + * Atomically removes the consumer from the {@link #observers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheDisposable<T> consumer) { + for (;;) { + CacheDisposable<T>[] current = observers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; } } + + if (j < 0) { + return; + } + CacheDisposable<T>[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheDisposable[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (observers.compareAndSet(current, next)) { + return; + } } - /** - * Removes the ReplayDisposable (if present) from the observers array atomically. - * @param p the target ReplayDisposable wrapping a downstream Observer with additional state - */ - @SuppressWarnings("unchecked") - public void removeChild(ReplayDisposable<T> p) { - for (;;) { - ReplayDisposable<T>[] a = observers.get(); - int n = a.length; - if (n == 0) { - return; - } - int j = -1; - for (int i = 0; i < n; i++) { - if (a[i].equals(p)) { - j = i; - break; - } - } - if (j < 0) { - return; - } - ReplayDisposable<T>[] b; - if (n == 1) { - b = EMPTY; + } + + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheDisposable<T> consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node<T> node = consumer.node; + Observer<? super T> downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // if the consumer got disposed, clear the node and quit + if (consumer.disposed) { + consumer.node = null; + return; + } + + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); } else { - b = new ReplayDisposable[n - 1]; - System.arraycopy(a, 0, b, 0, j); - System.arraycopy(a, j + 1, b, j, n - j - 1); + downstream.onComplete(); } - if (observers.compareAndSet(a, b)) { - return; + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; } - } - @Override - public void onSubscribe(Disposable s) { - connection.update(s); + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; + } } + } - /** - * Connects the cache to the source. - * Make sure this is called only once. - */ - public void connect() { - source.subscribe(this); - isConnected = true; + @Override + public void onSubscribe(Disposable d) { + // we can't do much with the upstream disposable + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node<T> n = new Node<T>(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; } - @Override - public void onNext(T t) { - if (!sourceDone) { - Object o = NotificationLite.next(t); - add(o); - for (ReplayDisposable<?> rp : observers.get()) { - rp.replay(); - } - } + size++; + for (CacheDisposable<T> consumer : observers.get()) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onError(Throwable e) { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.error(e); - add(o); - connection.dispose(); - for (ReplayDisposable<?> rp : observers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + error = t; + done = true; + for (CacheDisposable<T> consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); } - @SuppressWarnings("unchecked") - @Override - public void onComplete() { - if (!sourceDone) { - sourceDone = true; - Object o = NotificationLite.complete(); - add(o); - connection.dispose(); - for (ReplayDisposable<?> rp : observers.getAndSet(TERMINATED)) { - rp.replay(); - } - } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheDisposable<T> consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); } } /** - * Keeps track of the current request amount and the replay position for a child Observer. - * - * @param <T> + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param <T> the value type */ - static final class ReplayDisposable<T> - extends AtomicInteger + static final class CacheDisposable<T> extends AtomicInteger implements Disposable { - private static final long serialVersionUID = 7058506693698832024L; - /** The actual child subscriber. */ - final Observer<? super T> child; - /** The cache state object. */ - final CacheState<T> state; + private static final long serialVersionUID = 6770240836423125754L; - /** - * Contains the reference to the buffer segment in replay. - * Accessed after reading state.size() and when emitting == true. - */ - Object[] currentBuffer; - /** - * Contains the index into the currentBuffer where the next value is expected. - * Accessed after reading state.size() and when emitting == true. - */ - int currentIndexInBuffer; - /** - * Contains the absolute index up until the values have been replayed so far. - */ - int index; + final Observer<? super T> downstream; - /** Set if the ReplayDisposable has been cancelled/disposed. */ - volatile boolean cancelled; + final ObservableCache<T> parent; - ReplayDisposable(Observer<? super T> child, CacheState<T> state) { - this.child = child; - this.state = state; - } + Node<T> node; - @Override - public boolean isDisposed() { - return cancelled; + int offset; + + long index; + + volatile boolean disposed; + + /** + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items + */ + CacheDisposable(Observer<? super T> downstream, ObservableCache<T> parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; } + @Override public void dispose() { - if (!cancelled) { - cancelled = true; - state.removeChild(this); + if (!disposed) { + disposed = true; + parent.remove(this); } } - /** - * Continue replaying available values if there are requests for them. - */ - public void replay() { - // make sure there is only a single thread emitting - if (getAndIncrement() != 0) { - return; - } - - final Observer<? super T> child = this.child; - int missed = 1; - - for (;;) { + @Override + public boolean isDisposed() { + return disposed; + } + } - if (cancelled) { - return; - } + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param <T> the element type + */ + static final class Node<T> { - // read the size, if it is non-zero, we can safely read the head and - // read values up to the given absolute index - int s = state.size(); - if (s != 0) { - Object[] b = currentBuffer; - - // latch onto the very first buffer now that it is available. - if (b == null) { - b = state.head(); - currentBuffer = b; - } - final int n = b.length - 1; - int j = index; - int k = currentIndexInBuffer; - - while (j < s) { - if (cancelled) { - return; - } - if (k == n) { - b = (Object[])b[n]; - k = 0; - } - Object o = b[k]; - - if (NotificationLite.accept(o, child)) { - return; - } - - k++; - j++; - } - - if (cancelled) { - return; - } - - index = j; - currentIndexInBuffer = k; - currentBuffer = b; + /** + * The array of values held by this node. + */ + final T[] values; - } + /** + * The next node if not null. + */ + volatile Node<T> next; - missed = addAndGet(-missed); - if (missed == 0) { - break; - } - } + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java index 3d694dba9b..761fabd49e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java @@ -47,40 +47,38 @@ protected void subscribeActual(Observer<? super U> t) { } static final class CollectObserver<T, U> implements Observer<T>, Disposable { - final Observer<? super U> actual; + final Observer<? super U> downstream; final BiConsumer<? super U, ? super T> collector; final U u; - Disposable s; + Disposable upstream; boolean done; CollectObserver(Observer<? super U> actual, U u, BiConsumer<? super U, ? super T> collector) { - this.actual = actual; + this.downstream = actual; this.collector = collector; this.u = u; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -89,7 +87,7 @@ public void onNext(T t) { try { collector.accept(u, t); } catch (Throwable e) { - s.dispose(); + upstream.dispose(); onError(e); } } @@ -101,7 +99,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -110,8 +108,8 @@ public void onComplete() { return; } done = true; - actual.onNext(u); - actual.onComplete(); + downstream.onNext(u); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java index 92e36a26a7..59c34a0048 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java @@ -55,40 +55,38 @@ public Observable<U> fuseToObservable() { } static final class CollectObserver<T, U> implements Observer<T>, Disposable { - final SingleObserver<? super U> actual; + final SingleObserver<? super U> downstream; final BiConsumer<? super U, ? super T> collector; final U u; - Disposable s; + Disposable upstream; boolean done; CollectObserver(SingleObserver<? super U> actual, U u, BiConsumer<? super U, ? super T> collector) { - this.actual = actual; + this.downstream = actual; this.collector = collector; this.u = u; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -97,7 +95,7 @@ public void onNext(T t) { try { collector.accept(u, t); } catch (Throwable e) { - s.dispose(); + upstream.dispose(); onError(e); } } @@ -109,7 +107,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -118,7 +116,7 @@ public void onComplete() { return; } done = true; - actual.onSuccess(u); + downstream.onSuccess(u); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java index 808f4605cc..56b62cdfeb 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import java.util.Arrays; import java.util.concurrent.atomic.*; import io.reactivex.*; @@ -44,14 +43,13 @@ public ObservableCombineLatest(ObservableSource<? extends T>[] sources, this.delayError = delayError; } - @Override @SuppressWarnings("unchecked") - public void subscribeActual(Observer<? super R> s) { + public void subscribeActual(Observer<? super R> observer) { ObservableSource<? extends T>[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; for (ObservableSource<? extends T> p : sourcesIterable) { if (count == sources.length) { ObservableSource<? extends T>[] b = new ObservableSource[count + (count >> 2)]; @@ -65,22 +63,22 @@ public void subscribeActual(Observer<? super R> s) { } if (count == 0) { - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); return; } - LatestCoordinator<T, R> lc = new LatestCoordinator<T, R>(s, combiner, count, bufferSize, delayError); + LatestCoordinator<T, R> lc = new LatestCoordinator<T, R>(observer, combiner, count, bufferSize, delayError); lc.subscribe(sources); } static final class LatestCoordinator<T, R> extends AtomicInteger implements Disposable { private static final long serialVersionUID = 8567835998786448817L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super Object[], ? extends R> combiner; final CombinerObserver<T, R>[] observers; - final T[] latest; - final SpscLinkedArrayQueue<Object> queue; + Object[] latest; + final SpscLinkedArrayQueue<Object[]> queue; final boolean delayError; volatile boolean cancelled; @@ -96,22 +94,22 @@ static final class LatestCoordinator<T, R> extends AtomicInteger implements Disp LatestCoordinator(Observer<? super R> actual, Function<? super Object[], ? extends R> combiner, int count, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; this.delayError = delayError; - this.latest = (T[])new Object[count]; - this.observers = new CombinerObserver[count]; - this.queue = new SpscLinkedArrayQueue<Object>(bufferSize); + this.latest = new Object[count]; + CombinerObserver<T, R>[] as = new CombinerObserver[count]; + for (int i = 0; i < count; i++) { + as[i] = new CombinerObserver<T, R>(this, i); + } + this.observers = as; + this.queue = new SpscLinkedArrayQueue<Object[]>(bufferSize); } public void subscribe(ObservableSource<? extends T>[] sources) { Observer<T>[] as = observers; int len = as.length; - for (int i = 0; i < len; i++) { - as[i] = new CombinerObserver<T, R>(this, i); - } - lazySet(0); // release array contents - actual.onSubscribe(this); + downstream.onSubscribe(this); for (int i = 0; i < len; i++) { if (done || cancelled) { return; @@ -136,92 +134,56 @@ public boolean isDisposed() { return cancelled; } - void cancel(SpscLinkedArrayQueue<?> q) { - clear(q); - cancelSources(); - } - void cancelSources() { - for (CombinerObserver<T, R> s : observers) { - s.dispose(); + for (CombinerObserver<T, R> observer : observers) { + observer.dispose(); } } void clear(SpscLinkedArrayQueue<?> q) { synchronized (this) { - Arrays.fill(latest, null); + latest = null; } q.clear(); } - void combine(T value, int index) { - CombinerObserver<T, R> cs = observers[index]; - - int a; - int c; - int len; - boolean empty; - boolean f; - synchronized (this) { - if (cancelled) { - return; - } - len = latest.length; - T o = latest[index]; - a = active; - if (o == null) { - active = ++a; - } - c = complete; - if (value == null) { - complete = ++c; - } else { - latest[index] = value; - } - f = a == len; - // see if either all sources completed - empty = c == len - || (value == null && o == null); // or this source completed without any value - if (!empty) { - if (value != null && f) { - queue.offer(cs, latest.clone()); - } else - if (value == null && errors.get() != null) { - done = true; // if this source completed without a value - } - } else { - done = true; - } - } - if (!f && value != null) { - return; - } - drain(); - } void drain() { if (getAndIncrement() != 0) { return; } - final SpscLinkedArrayQueue<Object> q = queue; - final Observer<? super R> a = actual; + final SpscLinkedArrayQueue<Object[]> q = queue; + final Observer<? super R> a = downstream; final boolean delayError = this.delayError; int missed = 1; for (;;) { - if (checkTerminated(done, q.isEmpty(), a, q, delayError)) { - return; - } - for (;;) { + if (cancelled) { + clear(q); + return; + } + + if (!delayError && errors.get() != null) { + cancelSources(); + clear(q); + a.onError(errors.terminate()); + return; + } boolean d = done; - @SuppressWarnings("unchecked") - CombinerObserver<T, R> cs = (CombinerObserver<T, R>)q.poll(); - boolean empty = cs == null; + Object[] s = q.poll(); + boolean empty = s == null; - if (checkTerminated(d, empty, a, q, delayError)) { + if (d && empty) { + clear(q); + Throwable ex = errors.terminate(); + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } return; } @@ -229,16 +191,16 @@ void drain() { break; } - @SuppressWarnings("unchecked") - T[] array = (T[])q.poll(); - R v; + try { - v = ObjectHelper.requireNonNull(combiner.apply(array), "The combiner returned a null"); + v = ObjectHelper.requireNonNull(combiner.apply(s), "The combiner returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - cancelled = true; - cancel(q); + errors.addThrowable(ex); + cancelSources(); + clear(q); + ex = errors.terminate(); a.onError(ex); return; } @@ -253,53 +215,81 @@ void drain() { } } - - boolean checkTerminated(boolean d, boolean empty, Observer<?> a, SpscLinkedArrayQueue<?> q, boolean delayError) { - if (cancelled) { - cancel(q); - return true; + void innerNext(int index, T item) { + boolean shouldDrain = false; + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + Object o = latest[index]; + int a = active; + if (o == null) { + active = ++a; + } + latest[index] = item; + if (a == latest.length) { + queue.offer(latest.clone()); + shouldDrain = true; + } + } + if (shouldDrain) { + drain(); } - if (d) { + } + + void innerError(int index, Throwable ex) { + if (errors.addThrowable(ex)) { + boolean cancelOthers = true; if (delayError) { - if (empty) { - cancel(q); - Throwable e = errors.terminate(); - if (e != null) { - a.onError(e); - } else { - a.onComplete(); + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + + cancelOthers = latest[index] == null; + if (cancelOthers || ++complete == latest.length) { + done = true; } - return true; - } - } else { - Throwable e = errors.get(); - if (e != null) { - cancel(q); - a.onError(errors.terminate()); - return true; - } else - if (empty) { - clear(queue); - a.onComplete(); - return true; } } + if (cancelOthers) { + cancelSources(); + } + drain(); + } else { + RxJavaPlugins.onError(ex); } - return false; } - void onError(Throwable e) { - if (!errors.addThrowable(e)) { - RxJavaPlugins.onError(e); + void innerComplete(int index) { + boolean cancelOthers = false; + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + + cancelOthers = latest[index] == null; + if (cancelOthers || ++complete == latest.length) { + done = true; + } + } + if (cancelOthers) { + cancelSources(); } + drain(); } + } - static final class CombinerObserver<T, R> implements Observer<T> { + static final class CombinerObserver<T, R> extends AtomicReference<Disposable> implements Observer<T> { + private static final long serialVersionUID = -4823716997131257941L; + final LatestCoordinator<T, R> parent; - final int index; - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final int index; CombinerObserver(LatestCoordinator<T, R> parent, int index) { this.parent = parent; @@ -307,28 +297,27 @@ static final class CombinerObserver<T, R> implements Observer<T> { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override public void onNext(T t) { - parent.combine(t, index); + parent.innerNext(index, t); } @Override public void onError(Throwable t) { - parent.onError(t); - parent.combine(null, index); + parent.innerError(index, t); } @Override public void onComplete() { - parent.combine(null, index); + parent.innerComplete(index); } public void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java index b3cb5ff531..a59841d501 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.observable; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import io.reactivex.*; import io.reactivex.disposables.Disposable; @@ -40,33 +40,33 @@ public ObservableConcatMap(ObservableSource<T> source, Function<? super T, ? ext this.delayErrors = delayErrors; this.bufferSize = Math.max(8, bufferSize); } + @Override - public void subscribeActual(Observer<? super U> s) { + public void subscribeActual(Observer<? super U> observer) { - if (ObservableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + if (ObservableScalarXMap.tryScalarXMapSubscribe(source, observer, mapper)) { return; } if (delayErrors == ErrorMode.IMMEDIATE) { - SerializedObserver<U> serial = new SerializedObserver<U>(s); + SerializedObserver<U> serial = new SerializedObserver<U>(observer); source.subscribe(new SourceObserver<T, U>(serial, mapper, bufferSize)); } else { - source.subscribe(new ConcatMapDelayErrorObserver<T, U>(s, mapper, bufferSize, delayErrors == ErrorMode.END)); + source.subscribe(new ConcatMapDelayErrorObserver<T, U>(observer, mapper, bufferSize, delayErrors == ErrorMode.END)); } } static final class SourceObserver<T, U> extends AtomicInteger implements Observer<T>, Disposable { private static final long serialVersionUID = 8828587559905699186L; - final Observer<? super U> actual; - final SequentialDisposable sa; + final Observer<? super U> downstream; final Function<? super T, ? extends ObservableSource<? extends U>> mapper; - final Observer<U> inner; + final InnerObserver<U> inner; final int bufferSize; SimpleQueue<T> queue; - Disposable s; + Disposable upstream; volatile boolean active; @@ -78,19 +78,19 @@ static final class SourceObserver<T, U> extends AtomicInteger implements Observe SourceObserver(Observer<? super U> actual, Function<? super T, ? extends ObservableSource<? extends U>> mapper, int bufferSize) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.bufferSize = bufferSize; this.inner = new InnerObserver<U>(actual, this); - this.sa = new SequentialDisposable(); } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - if (s instanceof QueueDisposable) { + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") - QueueDisposable<T> qd = (QueueDisposable<T>) s; + QueueDisposable<T> qd = (QueueDisposable<T>) d; int m = qd.requestFusion(QueueDisposable.ANY); if (m == QueueDisposable.SYNC) { @@ -98,7 +98,7 @@ public void onSubscribe(Disposable s) { queue = qd; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); drain(); return; @@ -108,7 +108,7 @@ public void onSubscribe(Disposable s) { fusionMode = m; queue = qd; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } @@ -116,9 +116,10 @@ public void onSubscribe(Disposable s) { queue = new SpscLinkedArrayQueue<T>(bufferSize); - actual.onSubscribe(this); + downstream.onSubscribe(this); } } + @Override public void onNext(T t) { if (done) { @@ -129,6 +130,7 @@ public void onNext(T t) { } drain(); } + @Override public void onError(Throwable t) { if (done) { @@ -137,8 +139,9 @@ public void onError(Throwable t) { } done = true; dispose(); - actual.onError(t); + downstream.onError(t); } + @Override public void onComplete() { if (done) { @@ -161,18 +164,14 @@ public boolean isDisposed() { @Override public void dispose() { disposed = true; - sa.dispose(); - s.dispose(); + inner.dispose(); + upstream.dispose(); if (getAndIncrement() == 0) { queue.clear(); } } - void innerSubscribe(Disposable s) { - sa.update(s); - } - void drain() { if (getAndIncrement() != 0) { return; @@ -195,7 +194,7 @@ void drain() { Exceptions.throwIfFatal(ex); dispose(); queue.clear(); - actual.onError(ex); + downstream.onError(ex); return; } @@ -203,7 +202,7 @@ void drain() { if (d && empty) { disposed = true; - actual.onComplete(); + downstream.onComplete(); return; } @@ -216,7 +215,7 @@ void drain() { Exceptions.throwIfFatal(ex); dispose(); queue.clear(); - actual.onError(ex); + downstream.onError(ex); return; } @@ -231,33 +230,42 @@ void drain() { } } - static final class InnerObserver<U> implements Observer<U> { - final Observer<? super U> actual; + static final class InnerObserver<U> extends AtomicReference<Disposable> implements Observer<U> { + + private static final long serialVersionUID = -7449079488798789337L; + + final Observer<? super U> downstream; final SourceObserver<?, ?> parent; InnerObserver(Observer<? super U> actual, SourceObserver<?, ?> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } @Override - public void onSubscribe(Disposable s) { - parent.innerSubscribe(s); + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); } @Override public void onNext(U t) { - actual.onNext(t); + downstream.onNext(t); } + @Override public void onError(Throwable t) { parent.dispose(); - actual.onError(t); + downstream.onError(t); } + @Override public void onComplete() { parent.innerComplete(); } + + void dispose() { + DisposableHelper.dispose(this); + } } } @@ -265,10 +273,9 @@ static final class ConcatMapDelayErrorObserver<T, R> extends AtomicInteger implements Observer<T>, Disposable { - private static final long serialVersionUID = -6951100001833242599L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends ObservableSource<? extends R>> mapper; @@ -278,13 +285,11 @@ static final class ConcatMapDelayErrorObserver<T, R> final DelayErrorInnerObserver<R> observer; - final SequentialDisposable arbiter; - final boolean tillTheEnd; SimpleQueue<T> queue; - Disposable d; + Disposable upstream; volatile boolean active; @@ -297,19 +302,18 @@ static final class ConcatMapDelayErrorObserver<T, R> ConcatMapDelayErrorObserver(Observer<? super R> actual, Function<? super T, ? extends ObservableSource<? extends R>> mapper, int bufferSize, boolean tillTheEnd) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.bufferSize = bufferSize; this.tillTheEnd = tillTheEnd; this.error = new AtomicThrowable(); this.observer = new DelayErrorInnerObserver<R>(actual, this); - this.arbiter = new SequentialDisposable(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") @@ -321,7 +325,7 @@ public void onSubscribe(Disposable d) { queue = qd; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); drain(); return; @@ -330,7 +334,7 @@ public void onSubscribe(Disposable d) { sourceMode = m; queue = qd; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } @@ -338,7 +342,7 @@ public void onSubscribe(Disposable d) { queue = new SpscLinkedArrayQueue<T>(bufferSize); - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -374,8 +378,8 @@ public boolean isDisposed() { @Override public void dispose() { cancelled = true; - d.dispose(); - arbiter.dispose(); + upstream.dispose(); + observer.dispose(); } @SuppressWarnings("unchecked") @@ -384,7 +388,7 @@ void drain() { return; } - Observer<? super R> actual = this.actual; + Observer<? super R> actual = this.downstream; SimpleQueue<T> queue = this.queue; AtomicThrowable error = this.error; @@ -416,7 +420,7 @@ void drain() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancelled = true; - this.d.dispose(); + this.upstream.dispose(); error.addThrowable(ex); actual.onError(error.terminate()); return; @@ -444,7 +448,7 @@ void drain() { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancelled = true; - this.d.dispose(); + this.upstream.dispose(); queue.clear(); error.addThrowable(ex); actual.onError(error.terminate()); @@ -479,25 +483,27 @@ void drain() { } } - static final class DelayErrorInnerObserver<R> implements Observer<R> { + static final class DelayErrorInnerObserver<R> extends AtomicReference<Disposable> implements Observer<R> { + + private static final long serialVersionUID = 2620149119579502636L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final ConcatMapDelayErrorObserver<?, R> parent; DelayErrorInnerObserver(Observer<? super R> actual, ConcatMapDelayErrorObserver<?, R> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } @Override public void onSubscribe(Disposable d) { - parent.arbiter.replace(d); + DisposableHelper.replace(this, d); } @Override public void onNext(R value) { - actual.onNext(value); + downstream.onNext(value); } @Override @@ -505,7 +511,7 @@ public void onError(Throwable e) { ConcatMapDelayErrorObserver<?, R> p = parent; if (p.error.addThrowable(e)) { if (!p.tillTheEnd) { - p.d.dispose(); + p.upstream.dispose(); } p.active = false; p.drain(); @@ -520,6 +526,10 @@ public void onComplete() { p.active = false; p.drain(); } + + void dispose() { + DisposableHelper.dispose(this); + } } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java index 0fc83214e1..7028fdcf62 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java @@ -24,6 +24,7 @@ import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.observers.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; import io.reactivex.internal.util.*; import io.reactivex.plugins.RxJavaPlugins; @@ -59,7 +60,7 @@ static final class ConcatMapEagerMainObserver<T, R> private static final long serialVersionUID = 8080567949447303262L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends ObservableSource<? extends R>> mapper; @@ -75,7 +76,7 @@ static final class ConcatMapEagerMainObserver<T, R> SimpleQueue<T> queue; - Disposable d; + Disposable upstream; volatile boolean done; @@ -90,7 +91,7 @@ static final class ConcatMapEagerMainObserver<T, R> ConcatMapEagerMainObserver(Observer<? super R> actual, Function<? super T, ? extends ObservableSource<? extends R>> mapper, int maxConcurrency, int prefetch, ErrorMode errorMode) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.maxConcurrency = maxConcurrency; this.prefetch = prefetch; @@ -102,8 +103,8 @@ static final class ConcatMapEagerMainObserver<T, R> @SuppressWarnings("unchecked") @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; if (d instanceof QueueDisposable) { QueueDisposable<T> qd = (QueueDisposable<T>) d; @@ -114,7 +115,7 @@ public void onSubscribe(Disposable d) { queue = qd; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); drain(); return; @@ -123,15 +124,15 @@ public void onSubscribe(Disposable d) { sourceMode = m; queue = qd; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } } - queue = QueueDrainHelper.createQueue(prefetch); + queue = new SpscLinkedArrayQueue<T>(prefetch); - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -161,10 +162,21 @@ public void onComplete() { @Override public void dispose() { + if (cancelled) { + return; + } cancelled = true; + upstream.dispose(); + + drainAndDispose(); + } + + void drainAndDispose() { if (getAndIncrement() == 0) { - queue.clear(); - disposeAll(); + do { + queue.clear(); + disposeAll(); + } while (decrementAndGet() != 0); } } @@ -202,7 +214,7 @@ public void innerNext(InnerQueuedObserver<R> inner, R value) { public void innerError(InnerQueuedObserver<R> inner, Throwable e) { if (error.addThrowable(e)) { if (errorMode == ErrorMode.IMMEDIATE) { - d.dispose(); + upstream.dispose(); } inner.setDone(); drain(); @@ -227,7 +239,7 @@ public void drain() { SimpleQueue<T> q = queue; ArrayDeque<InnerQueuedObserver<R>> observers = this.observers; - Observer<? super R> a = this.actual; + Observer<? super R> a = this.downstream; ErrorMode errorMode = this.errorMode; outer: @@ -266,7 +278,7 @@ public void drain() { source = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null ObservableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); q.clear(); disposeAll(); error.addThrowable(ex); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java new file mode 100644 index 0000000000..5609455b33 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithCompletable<T> extends AbstractObservableWithUpstream<T, T> { + + final CompletableSource other; + + public ObservableConcatWithCompletable(Observable<T> source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new ConcatWithObserver<T>(observer, other)); + } + + static final class ConcatWithObserver<T> + extends AtomicReference<Disposable> + implements Observer<T>, CompletableObserver, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer<? super T> downstream; + + CompletableSource other; + + boolean inCompletable; + + ConcatWithObserver(Observer<? super T> actual, CompletableSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inCompletable) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + if (inCompletable) { + downstream.onComplete(); + } else { + inCompletable = true; + DisposableHelper.replace(this, null); + CompletableSource cs = other; + other = null; + cs.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java new file mode 100644 index 0000000000..ee0f5b9799 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Maybe, + * signal its success value followed by a completion or signal its error or completion signal as is. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithMaybe<T> extends AbstractObservableWithUpstream<T, T> { + + final MaybeSource<? extends T> other; + + public ObservableConcatWithMaybe(Observable<T> source, MaybeSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new ConcatWithObserver<T>(observer, other)); + } + + static final class ConcatWithObserver<T> + extends AtomicReference<Disposable> + implements Observer<T>, MaybeObserver<T>, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer<? super T> downstream; + + MaybeSource<? extends T> other; + + boolean inMaybe; + + ConcatWithObserver(Observer<? super T> actual, MaybeSource<? extends T> other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inMaybe) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onSuccess(T t) { + downstream.onNext(t); + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + if (inMaybe) { + downstream.onComplete(); + } else { + inMaybe = true; + DisposableHelper.replace(this, null); + MaybeSource<? extends T> ms = other; + other = null; + ms.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java new file mode 100644 index 0000000000..f3548e68a0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithSingle<T> extends AbstractObservableWithUpstream<T, T> { + + final SingleSource<? extends T> other; + + public ObservableConcatWithSingle(Observable<T> source, SingleSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new ConcatWithObserver<T>(observer, other)); + } + + static final class ConcatWithObserver<T> + extends AtomicReference<Disposable> + implements Observer<T>, SingleObserver<T>, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer<? super T> downstream; + + SingleSource<? extends T> other; + + boolean inSingle; + + ConcatWithObserver(Observer<? super T> actual, SingleSource<? extends T> other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inSingle) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onSuccess(T t) { + downstream.onNext(t); + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + inSingle = true; + DisposableHelper.replace(this, null); + SingleSource<? extends T> ss = other; + other = null; + ss.subscribe(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java index 41b8cc94a9..bae08e040b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java @@ -28,33 +28,32 @@ public void subscribeActual(Observer<? super Long> t) { } static final class CountObserver implements Observer<Object>, Disposable { - final Observer<? super Long> actual; + final Observer<? super Long> downstream; - Disposable s; + Disposable upstream; long count; - CountObserver(Observer<? super Long> actual) { - this.actual = actual; + CountObserver(Observer<? super Long> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override @@ -64,13 +63,13 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onNext(count); - actual.onComplete(); + downstream.onNext(count); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java index 2bc3febf7b..6e534b0dcc 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java @@ -36,34 +36,33 @@ public Observable<Long> fuseToObservable() { } static final class CountObserver implements Observer<Object>, Disposable { - final SingleObserver<? super Long> actual; + final SingleObserver<? super Long> downstream; - Disposable d; + Disposable upstream; long count; - CountObserver(SingleObserver<? super Long> actual) { - this.actual = actual; + CountObserver(SingleObserver<? super Long> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override @@ -73,14 +72,14 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - d = DisposableHelper.DISPOSED; - actual.onError(t); + upstream = DisposableHelper.DISPOSED; + downstream.onError(t); } @Override public void onComplete() { - d = DisposableHelper.DISPOSED; - actual.onSuccess(count); + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(count); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java index 153e44aef0..03ba3f1186 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java @@ -48,7 +48,6 @@ static final class CreateEmitter<T> extends AtomicReference<Disposable> implements ObservableEmitter<T>, Disposable { - private static final long serialVersionUID = -3434801548987643227L; final Observer<? super T> observer; @@ -126,6 +125,11 @@ public void dispose() { public boolean isDisposed() { return DisposableHelper.isDisposed(get()); } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } } /** @@ -261,8 +265,8 @@ void drainLoop() { } @Override - public void setDisposable(Disposable s) { - emitter.setDisposable(s); + public void setDisposable(Disposable d) { + emitter.setDisposable(d); } @Override @@ -279,6 +283,11 @@ public boolean isDisposed() { public ObservableEmitter<T> serialize() { return this; } + + @Override + public String toString() { + return emitter.toString(); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java index 2a70f8ba4e..db8b9d4794 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java @@ -39,10 +39,10 @@ public void subscribeActual(Observer<? super T> t) { static final class DebounceObserver<T, U> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Function<? super T, ? extends ObservableSource<U>> debounceSelector; - Disposable s; + Disposable upstream; final AtomicReference<Disposable> debouncer = new AtomicReference<Disposable>(); @@ -52,15 +52,15 @@ static final class DebounceObserver<T, U> DebounceObserver(Observer<? super T> actual, Function<? super T, ? extends ObservableSource<U>> debounceSelector) { - this.actual = actual; + this.downstream = actual; this.debounceSelector = debounceSelector; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -85,7 +85,7 @@ public void onNext(T t) { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - actual.onError(e); + downstream.onError(e); return; } @@ -99,7 +99,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { DisposableHelper.dispose(debouncer); - actual.onError(t); + downstream.onError(t); } @Override @@ -112,26 +112,28 @@ public void onComplete() { if (d != DisposableHelper.DISPOSED) { @SuppressWarnings("unchecked") DebounceInnerObserver<T, U> dis = (DebounceInnerObserver<T, U>)d; - dis.emit(); + if (dis != null) { + dis.emit(); + } DisposableHelper.dispose(debouncer); - actual.onComplete(); + downstream.onComplete(); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); DisposableHelper.dispose(debouncer); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } void emit(long idx, T value) { if (idx == index) { - actual.onNext(value); + downstream.onNext(value); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java index 7cdd59e3bb..49f490924a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java @@ -44,31 +44,31 @@ public void subscribeActual(Observer<? super T> t) { static final class DebounceTimedObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final long timeout; final TimeUnit unit; final Scheduler.Worker worker; - Disposable s; + Disposable upstream; - final AtomicReference<Disposable> timer = new AtomicReference<Disposable>(); + Disposable timer; volatile long index; boolean done; DebounceTimedObserver(Observer<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -80,18 +80,15 @@ public void onNext(T t) { long idx = index + 1; index = idx; - Disposable d = timer.get(); + Disposable d = timer; if (d != null) { d.dispose(); } DebounceEmitter<T> de = new DebounceEmitter<T>(t, idx, this); - if (timer.compareAndSet(d, de)) { - d = worker.schedule(de, timeout, unit); - - de.setResource(d); - } - + timer = de; + d = worker.schedule(de, timeout, unit); + de.setResource(d); } @Override @@ -100,8 +97,12 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } + Disposable d = timer; + if (d != null) { + d.dispose(); + } done = true; - actual.onError(t); + downstream.onError(t); worker.dispose(); } @@ -112,21 +113,23 @@ public void onComplete() { } done = true; - Disposable d = timer.get(); - if (d != DisposableHelper.DISPOSED) { - @SuppressWarnings("unchecked") - DebounceEmitter<T> de = (DebounceEmitter<T>)d; - if (de != null) { - de.run(); - } - actual.onComplete(); - worker.dispose(); + Disposable d = timer; + if (d != null) { + d.dispose(); } + + @SuppressWarnings("unchecked") + DebounceEmitter<T> de = (DebounceEmitter<T>)d; + if (de != null) { + de.run(); + } + downstream.onComplete(); + worker.dispose(); } @Override public void dispose() { - s.dispose(); + upstream.dispose(); worker.dispose(); } @@ -137,7 +140,7 @@ public boolean isDisposed() { void emit(long idx, T t, DebounceEmitter<T> emitter) { if (idx == index) { - actual.onNext(t); + downstream.onNext(t); emitter.dispose(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java index 37996e1332..adc2c29008 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java @@ -25,17 +25,18 @@ public final class ObservableDefer<T> extends Observable<T> { public ObservableDefer(Callable<? extends ObservableSource<? extends T>> supplier) { this.supplier = supplier; } + @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { ObservableSource<? extends T> pub; try { pub = ObjectHelper.requireNonNull(supplier.call(), "null ObservableSource supplied"); } catch (Throwable t) { Exceptions.throwIfFatal(t); - EmptyDisposable.error(t, s); + EmptyDisposable.error(t, observer); return; } - pub.subscribe(s); + pub.subscribe(observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java index adfbbd4b3f..8c07ed6878 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java @@ -38,30 +38,30 @@ public ObservableDelay(ObservableSource<T> source, long delay, TimeUnit unit, Sc @Override @SuppressWarnings("unchecked") public void subscribeActual(Observer<? super T> t) { - Observer<T> s; + Observer<T> observer; if (delayError) { - s = (Observer<T>)t; + observer = (Observer<T>)t; } else { - s = new SerializedObserver<T>(t); + observer = new SerializedObserver<T>(t); } Scheduler.Worker w = scheduler.createWorker(); - source.subscribe(new DelayObserver<T>(s, delay, unit, w, delayError)); + source.subscribe(new DelayObserver<T>(observer, delay, unit, w, delayError)); } static final class DelayObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final long delay; final TimeUnit unit; final Scheduler.Worker w; final boolean delayError; - Disposable s; + Disposable upstream; DelayObserver(Observer<? super T> actual, long delay, TimeUnit unit, Worker w, boolean delayError) { super(); - this.actual = actual; + this.downstream = actual; this.delay = delay; this.unit = unit; this.w = w; @@ -69,10 +69,10 @@ static final class DelayObserver<T> implements Observer<T>, Disposable { } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -93,7 +93,7 @@ public void onComplete() { @Override public void dispose() { - s.dispose(); + upstream.dispose(); w.dispose(); } @@ -111,7 +111,7 @@ final class OnNext implements Runnable { @Override public void run() { - actual.onNext(t); + downstream.onNext(t); } } @@ -125,7 +125,7 @@ final class OnError implements Runnable { @Override public void run() { try { - actual.onError(throwable); + downstream.onError(throwable); } finally { w.dispose(); } @@ -136,7 +136,7 @@ final class OnComplete implements Runnable { @Override public void run() { try { - actual.onComplete(); + downstream.onComplete(); } finally { w.dispose(); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java index 4fa032db0d..2f864152ee 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java @@ -15,69 +15,90 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; -public final class ObservableDematerialize<T> extends AbstractObservableWithUpstream<Notification<T>, T> { +public final class ObservableDematerialize<T, R> extends AbstractObservableWithUpstream<T, R> { - public ObservableDematerialize(ObservableSource<Notification<T>> source) { + final Function<? super T, ? extends Notification<R>> selector; + + public ObservableDematerialize(ObservableSource<T> source, Function<? super T, ? extends Notification<R>> selector) { super(source); + this.selector = selector; } @Override - public void subscribeActual(Observer<? super T> t) { - source.subscribe(new DematerializeObserver<T>(t)); + public void subscribeActual(Observer<? super R> observer) { + source.subscribe(new DematerializeObserver<T, R>(observer, selector)); } - static final class DematerializeObserver<T> implements Observer<Notification<T>>, Disposable { - final Observer<? super T> actual; + static final class DematerializeObserver<T, R> implements Observer<T>, Disposable { + final Observer<? super R> downstream; + + final Function<? super T, ? extends Notification<R>> selector; boolean done; - Disposable s; + Disposable upstream; - DematerializeObserver(Observer<? super T> actual) { - this.actual = actual; + DematerializeObserver(Observer<? super R> downstream, Function<? super T, ? extends Notification<R>> selector) { + this.downstream = downstream; + this.selector = selector; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override - public void onNext(Notification<T> t) { + public void onNext(T item) { if (done) { - if (t.isOnError()) { - RxJavaPlugins.onError(t.getError()); + if (item instanceof Notification) { + Notification<?> notification = (Notification<?>)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } } return; } - if (t.isOnError()) { - s.dispose(); - onError(t.getError()); + + Notification<R> notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; } - else if (t.isOnComplete()) { - s.dispose(); + if (notification.isOnError()) { + upstream.dispose(); + onError(notification.getError()); + } + else if (notification.isOnComplete()) { + upstream.dispose(); onComplete(); } else { - actual.onNext(t.getValue()); + downstream.onNext(notification.getValue()); } } @@ -89,8 +110,9 @@ public void onError(Throwable t) { } done = true; - actual.onError(t); + downstream.onError(t); } + @Override public void onComplete() { if (done) { @@ -98,7 +120,7 @@ public void onComplete() { } done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java index 797d06fcd5..897ac02ebd 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java @@ -31,60 +31,60 @@ public ObservableDetach(ObservableSource<T> source) { } @Override - protected void subscribeActual(Observer<? super T> s) { - source.subscribe(new DetachObserver<T>(s)); + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new DetachObserver<T>(observer)); } static final class DetachObserver<T> implements Observer<T>, Disposable { - Observer<? super T> actual; + Observer<? super T> downstream; - Disposable s; + Disposable upstream; - DetachObserver(Observer<? super T> actual) { - this.actual = actual; + DetachObserver(Observer<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - Disposable s = this.s; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asObserver(); - s.dispose(); + Disposable d = this.upstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); + d.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - Observer<? super T> a = actual; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asObserver(); + Observer<? super T> a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); a.onError(t); } @Override public void onComplete() { - Observer<? super T> a = actual; - this.s = EmptyComponent.INSTANCE; - this.actual = EmptyComponent.asObserver(); + Observer<? super T> a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); a.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java index 4e625ee44c..6ebe127e4e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java @@ -82,10 +82,10 @@ public void onNext(T value) { } if (b) { - actual.onNext(value); + downstream.onNext(value); } } else { - actual.onNext(null); + downstream.onNext(null); } } @@ -96,7 +96,7 @@ public void onError(Throwable e) { } else { done = true; collection.clear(); - actual.onError(e); + downstream.onError(e); } } @@ -105,7 +105,7 @@ public void onComplete() { if (!done) { done = true; collection.clear(); - actual.onComplete(); + downstream.onComplete(); } } @@ -118,7 +118,7 @@ public int requestFusion(int mode) { @Override public T poll() throws Exception { for (;;) { - T v = qs.poll(); + T v = qd.poll(); if (v == null || collection.add(ObjectHelper.requireNonNull(keySelector.apply(v), "The keySelector returned a null key"))) { return v; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java index e5eb1e7cdf..866efd3210 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java @@ -31,8 +31,8 @@ public ObservableDistinctUntilChanged(ObservableSource<T> source, Function<? sup } @Override - protected void subscribeActual(Observer<? super T> s) { - source.subscribe(new DistinctUntilChangedObserver<T, K>(s, keySelector, comparer)); + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new DistinctUntilChangedObserver<T, K>(observer, keySelector, comparer)); } static final class DistinctUntilChangedObserver<T, K> extends BasicFuseableObserver<T, T> { @@ -59,7 +59,7 @@ public void onNext(T t) { return; } if (sourceMode != NONE) { - actual.onNext(t); + downstream.onNext(t); return; } @@ -82,7 +82,7 @@ public void onNext(T t) { return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -94,7 +94,7 @@ public int requestFusion(int mode) { @Override public T poll() throws Exception { for (;;) { - T v = qs.poll(); + T v = qd.poll(); if (v == null) { return null; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java index dc01638d96..c84f3571ab 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java @@ -14,17 +14,16 @@ package io.reactivex.internal.operators.observable; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.annotations.Nullable; import io.reactivex.functions.Consumer; import io.reactivex.internal.observers.BasicFuseableObserver; /** * Calls a consumer after pushing the current item to the downstream. + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class ObservableDoAfterNext<T> extends AbstractObservableWithUpstream<T, T> { final Consumer<? super T> onAfterNext; @@ -35,8 +34,8 @@ public ObservableDoAfterNext(ObservableSource<T> source, Consumer<? super T> onA } @Override - protected void subscribeActual(Observer<? super T> s) { - source.subscribe(new DoAfterObserver<T>(s, onAfterNext)); + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new DoAfterObserver<T>(observer, onAfterNext)); } static final class DoAfterObserver<T> extends BasicFuseableObserver<T, T> { @@ -50,7 +49,7 @@ static final class DoAfterObserver<T> extends BasicFuseableObserver<T, T> { @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); if (sourceMode == NONE) { try { @@ -69,7 +68,7 @@ public int requestFusion(int mode) { @Nullable @Override public T poll() throws Exception { - T v = qs.poll(); + T v = qd.poll(); if (v != null) { onAfterNext.accept(v); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java index cf474c164d..bc305afde5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.annotations.Nullable; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; @@ -26,11 +25,10 @@ /** * Execute an action after an onError, onComplete or a dispose event. - * + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class ObservableDoFinally<T> extends AbstractObservableWithUpstream<T, T> { final Action onFinally; @@ -41,68 +39,68 @@ public ObservableDoFinally(ObservableSource<T> source, Action onFinally) { } @Override - protected void subscribeActual(Observer<? super T> s) { - source.subscribe(new DoFinallyObserver<T>(s, onFinally)); + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new DoFinallyObserver<T>(observer, onFinally)); } static final class DoFinallyObserver<T> extends BasicIntQueueDisposable<T> implements Observer<T> { private static final long serialVersionUID = 4109457741734051389L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final Action onFinally; - Disposable d; + Disposable upstream; QueueDisposable<T> qd; boolean syncFused; DoFinallyObserver(Observer<? super T> actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @SuppressWarnings("unchecked") @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; if (d instanceof QueueDisposable) { this.qd = (QueueDisposable<T>)d; } - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); runFinally(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); runFinally(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java index 14ad8fe3df..a88218cbd5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java @@ -43,13 +43,13 @@ public void subscribeActual(Observer<? super T> t) { } static final class DoOnEachObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Consumer<? super T> onNext; final Consumer<? super Throwable> onError; final Action onComplete; final Action onAfterTerminate; - Disposable s; + Disposable upstream; boolean done; @@ -59,7 +59,7 @@ static final class DoOnEachObserver<T> implements Observer<T>, Disposable { Consumer<? super Throwable> onError, Action onComplete, Action onAfterTerminate) { - this.actual = actual; + this.downstream = actual; this.onNext = onNext; this.onError = onError; this.onComplete = onComplete; @@ -67,25 +67,23 @@ static final class DoOnEachObserver<T> implements Observer<T>, Disposable { } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -95,12 +93,12 @@ public void onNext(T t) { onNext.accept(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -116,7 +114,7 @@ public void onError(Throwable t) { Exceptions.throwIfFatal(e); t = new CompositeException(t, e); } - actual.onError(t); + downstream.onError(t); try { onAfterTerminate.run(); @@ -140,7 +138,7 @@ public void onComplete() { } done = true; - actual.onComplete(); + downstream.onComplete(); try { onAfterTerminate.run(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java index 0870ea812d..5897fddfed 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java @@ -31,50 +31,49 @@ public ObservableElementAt(ObservableSource<T> source, long index, T defaultValu this.defaultValue = defaultValue; this.errorOnFewer = errorOnFewer; } + @Override public void subscribeActual(Observer<? super T> t) { source.subscribe(new ElementAtObserver<T>(t, index, defaultValue, errorOnFewer)); } static final class ElementAtObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final long index; final T defaultValue; final boolean errorOnFewer; - Disposable s; + Disposable upstream; long count; boolean done; ElementAtObserver(Observer<? super T> actual, long index, T defaultValue, boolean errorOnFewer) { - this.actual = actual; + this.downstream = actual; this.index = index; this.defaultValue = defaultValue; this.errorOnFewer = errorOnFewer; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -83,9 +82,9 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.dispose(); - actual.onNext(t); - actual.onComplete(); + upstream.dispose(); + downstream.onNext(t); + downstream.onComplete(); return; } count = c + 1; @@ -98,7 +97,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -107,12 +106,12 @@ public void onComplete() { done = true; T v = defaultValue; if (v == null && errorOnFewer) { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } else { if (v != null) { - actual.onNext(v); + downstream.onNext(v); } - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java index 1f7db1e68f..921edd6ab3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java @@ -26,6 +26,7 @@ public ObservableElementAtMaybe(ObservableSource<T> source, long index) { this.source = source; this.index = index; } + @Override public void subscribeActual(MaybeObserver<? super T> t) { source.subscribe(new ElementAtObserver<T>(t, index)); @@ -37,40 +38,38 @@ public Observable<T> fuseToObservable() { } static final class ElementAtObserver<T> implements Observer<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final long index; - Disposable s; + Disposable upstream; long count; boolean done; ElementAtObserver(MaybeObserver<? super T> actual, long index) { - this.actual = actual; + this.downstream = actual; this.index = index; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -79,8 +78,8 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.dispose(); - actual.onSuccess(t); + upstream.dispose(); + downstream.onSuccess(t); return; } count = c + 1; @@ -93,14 +92,14 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java index dd7e9973f8..7de1fa7bef 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java @@ -43,42 +43,40 @@ public Observable<T> fuseToObservable() { } static final class ElementAtObserver<T> implements Observer<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final long index; final T defaultValue; - Disposable s; + Disposable upstream; long count; boolean done; ElementAtObserver(SingleObserver<? super T> actual, long index, T defaultValue) { - this.actual = actual; + this.downstream = actual; this.index = index; this.defaultValue = defaultValue; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -87,8 +85,8 @@ public void onNext(T t) { long c = count; if (c == index) { done = true; - s.dispose(); - actual.onSuccess(t); + upstream.dispose(); + downstream.onSuccess(t); return; } count = c + 1; @@ -101,7 +99,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -112,9 +110,9 @@ public void onComplete() { T v = defaultValue; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java index 29076de2b4..b0eeb95c56 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java @@ -25,8 +25,9 @@ public final class ObservableError<T> extends Observable<T> { public ObservableError(Callable<? extends Throwable> errorSupplier) { this.errorSupplier = errorSupplier; } + @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { Throwable error; try { error = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources."); @@ -34,6 +35,6 @@ public void subscribeActual(Observer<? super T> s) { Exceptions.throwIfFatal(t); error = t; } - EmptyDisposable.error(error, s); + EmptyDisposable.error(error, observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java index 108c44a88c..c9ec142a76 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java @@ -26,8 +26,8 @@ public ObservableFilter(ObservableSource<T> source, Predicate<? super T> predica } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new FilterObserver<T>(s, predicate)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new FilterObserver<T>(observer, predicate)); } static final class FilterObserver<T> extends BasicFuseableObserver<T, T> { @@ -49,10 +49,10 @@ public void onNext(T t) { return; } if (b) { - actual.onNext(t); + downstream.onNext(t); } } else { - actual.onNext(null); + downstream.onNext(null); } } @@ -65,7 +65,7 @@ public int requestFusion(int mode) { @Override public T poll() throws Exception { for (;;) { - T v = qs.poll(); + T v = qd.poll(); if (v == null || filter.test(v)) { return v; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java index d67a850783..73a5306513 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java @@ -59,7 +59,7 @@ static final class MergeObserver<T, U> extends AtomicInteger implements Disposab private static final long serialVersionUID = -2117620485640801370L; - final Observer<? super U> actual; + final Observer<? super U> downstream; final Function<? super T, ? extends ObservableSource<? extends U>> mapper; final boolean delayErrors; final int maxConcurrency; @@ -79,7 +79,7 @@ static final class MergeObserver<T, U> extends AtomicInteger implements Disposab static final InnerObserver<?, ?>[] CANCELLED = new InnerObserver<?, ?>[0]; - Disposable s; + Disposable upstream; long uniqueId; long lastId; @@ -91,7 +91,7 @@ static final class MergeObserver<T, U> extends AtomicInteger implements Disposab MergeObserver(Observer<? super U> actual, Function<? super T, ? extends ObservableSource<? extends U>> mapper, boolean delayErrors, int maxConcurrency, int bufferSize) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.maxConcurrency = maxConcurrency; @@ -103,10 +103,10 @@ static final class MergeObserver<T, U> extends AtomicInteger implements Disposab } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -121,7 +121,7 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } @@ -143,16 +143,19 @@ public void onNext(T t) { void subscribeInner(ObservableSource<? extends U> p) { for (;;) { if (p instanceof Callable) { - tryEmitScalar(((Callable<? extends U>)p)); - - if (maxConcurrency != Integer.MAX_VALUE) { + if (tryEmitScalar(((Callable<? extends U>)p)) && maxConcurrency != Integer.MAX_VALUE) { + boolean empty = false; synchronized (this) { p = sources.poll(); if (p == null) { wip--; - break; + empty = true; } } + if (empty) { + drain(); + break; + } } else { break; } @@ -214,7 +217,7 @@ void removeInner(InnerObserver<T, U> inner) { } } - void tryEmitScalar(Callable<? extends U> value) { + boolean tryEmitScalar(Callable<? extends U> value) { U u; try { u = value.call(); @@ -222,18 +225,17 @@ void tryEmitScalar(Callable<? extends U> value) { Exceptions.throwIfFatal(ex); errors.addThrowable(ex); drain(); - return; + return true; } if (u == null) { - return; + return true; } - if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(u); + downstream.onNext(u); if (decrementAndGet() == 0) { - return; + return true; } } else { SimplePlainQueue<U> q = queue; @@ -248,18 +250,19 @@ void tryEmitScalar(Callable<? extends U> value) { if (!q.offer(u)) { onError(new IllegalStateException("Scalar queue full?!")); - return; + return true; } if (getAndIncrement() != 0) { - return; + return false; } } drainLoop(); + return true; } void tryEmit(U value, InnerObserver<T, U> inner) { if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(value); + downstream.onNext(value); if (decrementAndGet() == 0) { return; } @@ -325,34 +328,38 @@ void drain() { } void drainLoop() { - final Observer<? super U> child = this.actual; + final Observer<? super U> child = this.downstream; int missed = 1; for (;;) { if (checkTerminate()) { return; } + int innerCompleted = 0; SimplePlainQueue<U> svq = queue; if (svq != null) { for (;;) { - U o; - for (;;) { - if (checkTerminate()) { - return; - } - - o = svq.poll(); + if (checkTerminate()) { + return; + } - if (o == null) { - break; - } + U o = svq.poll(); - child.onNext(o); - } if (o == null) { break; } + + child.onNext(o); + innerCompleted++; + } + } + + if (innerCompleted != 0) { + if (maxConcurrency != Integer.MAX_VALUE) { + subscribeMore(innerCompleted); + innerCompleted = 0; } + continue; } boolean d = done; @@ -360,7 +367,14 @@ void drainLoop() { InnerObserver<?, ?>[] inner = observers.get(); int n = inner.length; - if (d && (svq == null || svq.isEmpty()) && n == 0) { + int nSources = 0; + if (maxConcurrency != Integer.MAX_VALUE) { + synchronized (this) { + nSources = sources.size(); + } + } + + if (d && (svq == null || svq.isEmpty()) && n == 0 && nSources == 0) { Throwable ex = errors.terminate(); if (ex != ExceptionHelper.TERMINATED) { if (ex == null) { @@ -372,7 +386,6 @@ void drainLoop() { return; } - boolean innerCompleted = false; if (n != 0) { long startId = lastId; int index = lastIndex; @@ -402,19 +415,13 @@ void drainLoop() { if (checkTerminate()) { return; } + @SuppressWarnings("unchecked") InnerObserver<T, U> is = (InnerObserver<T, U>)inner[j]; - - for (;;) { - if (checkTerminate()) { - return; - } - SimpleQueue<U> q = is.queue; - if (q == null) { - break; - } - U o; + SimpleQueue<U> q = is.queue; + if (q != null) { for (;;) { + U o; try { o = q.poll(); } catch (Throwable ex) { @@ -425,8 +432,11 @@ void drainLoop() { return; } removeInner(is); - innerCompleted = true; - i++; + innerCompleted++; + j++; + if (j == n) { + j = 0; + } continue sourceLoop; } if (o == null) { @@ -439,10 +449,8 @@ void drainLoop() { return; } } - if (o == null) { - break; - } } + boolean innerDone = is.done; SimpleQueue<U> innerQueue = is.queue; if (innerDone && (innerQueue == null || innerQueue.isEmpty())) { @@ -450,7 +458,7 @@ void drainLoop() { if (checkTerminate()) { return; } - innerCompleted = true; + innerCompleted++; } j++; @@ -462,20 +470,14 @@ void drainLoop() { lastId = inner[j].id; } - if (innerCompleted) { + if (innerCompleted != 0) { if (maxConcurrency != Integer.MAX_VALUE) { - ObservableSource<? extends U> p; - synchronized (this) { - p = sources.poll(); - if (p == null) { - wip--; - continue; - } - } - subscribeInner(p); + subscribeMore(innerCompleted); + innerCompleted = 0; } continue; } + missed = addAndGet(-missed); if (missed == 0) { break; @@ -483,6 +485,20 @@ void drainLoop() { } } + void subscribeMore(int innerCompleted) { + while (innerCompleted-- != 0) { + ObservableSource<? extends U> p; + synchronized (this) { + p = sources.poll(); + if (p == null) { + wip--; + continue; + } + } + subscribeInner(p); + } + } + boolean checkTerminate() { if (cancelled) { return true; @@ -492,7 +508,7 @@ boolean checkTerminate() { disposeAll(); e = errors.terminate(); if (e != ExceptionHelper.TERMINATED) { - actual.onError(e); + downstream.onError(e); } return true; } @@ -500,7 +516,7 @@ boolean checkTerminate() { } boolean disposeAll() { - s.dispose(); + upstream.dispose(); InnerObserver<?, ?>[] a = observers.get(); if (a != CANCELLED) { a = observers.getAndSet(CANCELLED); @@ -531,12 +547,13 @@ static final class InnerObserver<T, U> extends AtomicReference<Disposable> this.id = id; this.parent = parent; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.setOnce(this, s)) { - if (s instanceof QueueDisposable) { + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") - QueueDisposable<U> qd = (QueueDisposable<U>) s; + QueueDisposable<U> qd = (QueueDisposable<U>) d; int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); if (m == QueueDisposable.SYNC) { @@ -553,6 +570,7 @@ public void onSubscribe(Disposable s) { } } } + @Override public void onNext(U t) { if (fusionMode == QueueDisposable.NONE) { @@ -561,6 +579,7 @@ public void onNext(U t) { parent.drain(); } } + @Override public void onError(Throwable t) { if (parent.errors.addThrowable(t)) { @@ -573,6 +592,7 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); } } + @Override public void onComplete() { done = true; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java index dc6c1e8dd7..727d0bc2de 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java @@ -52,7 +52,7 @@ static final class FlatMapCompletableMainObserver<T> extends BasicIntQueueDispos implements Observer<T> { private static final long serialVersionUID = 8443155186132538303L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final AtomicThrowable errors; @@ -62,12 +62,12 @@ static final class FlatMapCompletableMainObserver<T> extends BasicIntQueueDispos final CompositeDisposable set; - Disposable d; + Disposable upstream; volatile boolean disposed; FlatMapCompletableMainObserver(Observer<? super T> observer, Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { - this.actual = observer; + this.downstream = observer; this.mapper = mapper; this.delayErrors = delayErrors; this.errors = new AtomicThrowable(); @@ -77,10 +77,10 @@ static final class FlatMapCompletableMainObserver<T> extends BasicIntQueueDispos @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -92,7 +92,7 @@ public void onNext(T value) { cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -112,13 +112,13 @@ public void onError(Throwable e) { if (delayErrors) { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } else { dispose(); if (getAndSet(0) > 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } } else { @@ -131,9 +131,9 @@ public void onComplete() { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } } @@ -141,13 +141,13 @@ public void onComplete() { @Override public void dispose() { disposed = true; - d.dispose(); + upstream.dispose(); set.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Nullable diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java index 91734a1196..67691a11f8 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java @@ -57,7 +57,7 @@ public Observable<T> fuseToObservable() { static final class FlatMapCompletableMainObserver<T> extends AtomicInteger implements Disposable, Observer<T> { private static final long serialVersionUID = 8443155186132538303L; - final CompletableObserver actual; + final CompletableObserver downstream; final AtomicThrowable errors; @@ -67,12 +67,12 @@ static final class FlatMapCompletableMainObserver<T> extends AtomicInteger imple final CompositeDisposable set; - Disposable d; + Disposable upstream; volatile boolean disposed; FlatMapCompletableMainObserver(CompletableObserver observer, Function<? super T, ? extends CompletableSource> mapper, boolean delayErrors) { - this.actual = observer; + this.downstream = observer; this.mapper = mapper; this.delayErrors = delayErrors; this.errors = new AtomicThrowable(); @@ -82,10 +82,10 @@ static final class FlatMapCompletableMainObserver<T> extends AtomicInteger imple @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -97,7 +97,7 @@ public void onNext(T value) { cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -117,13 +117,13 @@ public void onError(Throwable e) { if (delayErrors) { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } else { dispose(); if (getAndSet(0) > 0) { Throwable ex = errors.terminate(); - actual.onError(ex); + downstream.onError(ex); } } } else { @@ -136,9 +136,9 @@ public void onComplete() { if (decrementAndGet() == 0) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } } } @@ -146,13 +146,13 @@ public void onComplete() { @Override public void dispose() { disposed = true; - d.dispose(); + upstream.dispose(); set.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } void innerComplete(InnerObserver inner) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java index 930f339175..122572ac50 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java @@ -44,8 +44,8 @@ public ObservableFlatMapMaybe(ObservableSource<T> source, Function<? super T, ? } @Override - protected void subscribeActual(Observer<? super R> s) { - source.subscribe(new FlatMapMaybeObserver<T, R>(s, mapper, delayErrors)); + protected void subscribeActual(Observer<? super R> observer) { + source.subscribe(new FlatMapMaybeObserver<T, R>(observer, mapper, delayErrors)); } static final class FlatMapMaybeObserver<T, R> @@ -54,7 +54,7 @@ static final class FlatMapMaybeObserver<T, R> private static final long serialVersionUID = 8600231336733376951L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final boolean delayErrors; @@ -68,13 +68,13 @@ static final class FlatMapMaybeObserver<T, R> final AtomicReference<SpscLinkedArrayQueue<R>> queue; - Disposable d; + Disposable upstream; volatile boolean cancelled; FlatMapMaybeObserver(Observer<? super R> actual, Function<? super T, ? extends MaybeSource<? extends R>> mapper, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.set = new CompositeDisposable(); @@ -85,10 +85,10 @@ static final class FlatMapMaybeObserver<T, R> @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -100,7 +100,7 @@ public void onNext(T t) { ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -136,7 +136,7 @@ public void onComplete() { @Override public void dispose() { cancelled = true; - d.dispose(); + upstream.dispose(); set.dispose(); } @@ -148,7 +148,7 @@ public boolean isDisposed() { void innerSuccess(InnerObserver inner, R value) { set.delete(inner); if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(value); + downstream.onNext(value); boolean d = active.decrementAndGet() == 0; SpscLinkedArrayQueue<R> q = queue.get(); @@ -156,9 +156,9 @@ void innerSuccess(InnerObserver inner, R value) { if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -195,7 +195,7 @@ void innerError(InnerObserver inner, Throwable e) { set.delete(inner); if (errors.addThrowable(e)) { if (!delayErrors) { - d.dispose(); + upstream.dispose(); set.dispose(); } active.decrementAndGet(); @@ -215,9 +215,9 @@ void innerComplete(InnerObserver inner) { if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -246,7 +246,7 @@ void clear() { void drainLoop() { int missed = 1; - Observer<? super R> a = actual; + Observer<? super R> a = downstream; AtomicInteger n = active; AtomicReference<SpscLinkedArrayQueue<R>> qr = queue; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java index 34f298fa2c..bedda3cb49 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java @@ -44,8 +44,8 @@ public ObservableFlatMapSingle(ObservableSource<T> source, Function<? super T, ? } @Override - protected void subscribeActual(Observer<? super R> s) { - source.subscribe(new FlatMapSingleObserver<T, R>(s, mapper, delayErrors)); + protected void subscribeActual(Observer<? super R> observer) { + source.subscribe(new FlatMapSingleObserver<T, R>(observer, mapper, delayErrors)); } static final class FlatMapSingleObserver<T, R> @@ -54,7 +54,7 @@ static final class FlatMapSingleObserver<T, R> private static final long serialVersionUID = 8600231336733376951L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final boolean delayErrors; @@ -68,13 +68,13 @@ static final class FlatMapSingleObserver<T, R> final AtomicReference<SpscLinkedArrayQueue<R>> queue; - Disposable d; + Disposable upstream; volatile boolean cancelled; FlatMapSingleObserver(Observer<? super R> actual, Function<? super T, ? extends SingleSource<? extends R>> mapper, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.delayErrors = delayErrors; this.set = new CompositeDisposable(); @@ -85,10 +85,10 @@ static final class FlatMapSingleObserver<T, R> @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -100,7 +100,7 @@ public void onNext(T t) { ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -136,7 +136,7 @@ public void onComplete() { @Override public void dispose() { cancelled = true; - d.dispose(); + upstream.dispose(); set.dispose(); } @@ -148,7 +148,7 @@ public boolean isDisposed() { void innerSuccess(InnerObserver inner, R value) { set.delete(inner); if (get() == 0 && compareAndSet(0, 1)) { - actual.onNext(value); + downstream.onNext(value); boolean d = active.decrementAndGet() == 0; SpscLinkedArrayQueue<R> q = queue.get(); @@ -156,9 +156,9 @@ void innerSuccess(InnerObserver inner, R value) { if (d && (q == null || q.isEmpty())) { Throwable ex = errors.terminate(); if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } return; } @@ -195,7 +195,7 @@ void innerError(InnerObserver inner, Throwable e) { set.delete(inner); if (errors.addThrowable(e)) { if (!delayErrors) { - d.dispose(); + upstream.dispose(); set.dispose(); } active.decrementAndGet(); @@ -220,7 +220,7 @@ void clear() { void drainLoop() { int missed = 1; - Observer<? super R> a = actual; + Observer<? super R> a = downstream; AtomicInteger n = active; AtomicReference<SpscLinkedArrayQueue<R>> qr = queue; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java index 1781f0a03d..74b1839bb8 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java @@ -45,29 +45,29 @@ protected void subscribeActual(Observer<? super R> observer) { } static final class FlattenIterableObserver<T, R> implements Observer<T>, Disposable { - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; - Disposable d; + Disposable upstream; FlattenIterableObserver(Observer<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T value) { - if (d == DisposableHelper.DISPOSED) { + if (upstream == DisposableHelper.DISPOSED) { return; } @@ -77,12 +77,12 @@ public void onNext(T value) { it = mapper.apply(value).iterator(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } - Observer<? super R> a = actual; + Observer<? super R> a = downstream; for (;;) { boolean b; @@ -91,7 +91,7 @@ public void onNext(T value) { b = it.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -103,7 +103,7 @@ public void onNext(T value) { v = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); return; } @@ -117,32 +117,32 @@ public void onNext(T value) { @Override public void onError(Throwable e) { - if (d == DisposableHelper.DISPOSED) { + if (upstream == DisposableHelper.DISPOSED) { RxJavaPlugins.onError(e); return; } - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void onComplete() { - if (d == DisposableHelper.DISPOSED) { + if (upstream == DisposableHelper.DISPOSED) { return; } - d = DisposableHelper.DISPOSED; - actual.onComplete(); + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java index 4ac881ceec..9a04a4fcc3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java @@ -23,11 +23,12 @@ public final class ObservableFromArray<T> extends Observable<T> { public ObservableFromArray(T[] array) { this.array = array; } + @Override - public void subscribeActual(Observer<? super T> s) { - FromArrayDisposable<T> d = new FromArrayDisposable<T>(s, array); + public void subscribeActual(Observer<? super T> observer) { + FromArrayDisposable<T> d = new FromArrayDisposable<T>(observer, array); - s.onSubscribe(d); + observer.onSubscribe(d); if (d.fusionMode) { return; @@ -38,7 +39,7 @@ public void subscribeActual(Observer<? super T> s) { static final class FromArrayDisposable<T> extends BasicQueueDisposable<T> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final T[] array; @@ -49,7 +50,7 @@ static final class FromArrayDisposable<T> extends BasicQueueDisposable<T> { volatile boolean disposed; FromArrayDisposable(Observer<? super T> actual, T[] array) { - this.actual = actual; + this.downstream = actual; this.array = array; } @@ -101,13 +102,13 @@ void run() { for (int i = 0; i < n && !isDisposed(); i++) { T value = a[i]; if (value == null) { - actual.onError(new NullPointerException("The " + i + "th element is null")); + downstream.onError(new NullPointerException("The element at index " + i + " is null")); return; } - actual.onNext(value); + downstream.onNext(value); } if (!isDisposed()) { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java index cb2671b852..fe3c364793 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java @@ -30,10 +30,11 @@ public final class ObservableFromCallable<T> extends Observable<T> implements Ca public ObservableFromCallable(Callable<? extends T> callable) { this.callable = callable; } + @Override - public void subscribeActual(Observer<? super T> s) { - DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(s); - s.onSubscribe(d); + public void subscribeActual(Observer<? super T> observer) { + DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(observer); + observer.onSubscribe(d); if (d.isDisposed()) { return; } @@ -43,7 +44,7 @@ public void subscribeActual(Observer<? super T> s) { } catch (Throwable e) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { - s.onError(e); + observer.onError(e); } else { RxJavaPlugins.onError(e); } @@ -54,6 +55,6 @@ public void subscribeActual(Observer<? super T> s) { @Override public T call() throws Exception { - return callable.call(); + return ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java index 6a58d50680..ec6e90275d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java @@ -32,9 +32,9 @@ public ObservableFromFuture(Future<? extends T> future, long timeout, TimeUnit u } @Override - public void subscribeActual(Observer<? super T> s) { - DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(s); - s.onSubscribe(d); + public void subscribeActual(Observer<? super T> observer) { + DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(observer); + observer.onSubscribe(d); if (!d.isDisposed()) { T v; try { @@ -42,7 +42,7 @@ public void subscribeActual(Observer<? super T> s) { } catch (Throwable ex) { Exceptions.throwIfFatal(ex); if (!d.isDisposed()) { - s.onError(ex); + observer.onError(ex); } return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java index 3a8b7853d5..f937f4ded5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java @@ -29,13 +29,13 @@ public ObservableFromIterable(Iterable<? extends T> source) { } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { Iterator<? extends T> it; try { it = source.iterator(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } boolean hasNext; @@ -43,16 +43,16 @@ public void subscribeActual(Observer<? super T> s) { hasNext = it.hasNext(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } if (!hasNext) { - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); return; } - FromIterableDisposable<T> d = new FromIterableDisposable<T>(s, it); - s.onSubscribe(d); + FromIterableDisposable<T> d = new FromIterableDisposable<T>(observer, it); + observer.onSubscribe(d); if (!d.fusionMode) { d.run(); @@ -61,7 +61,7 @@ public void subscribeActual(Observer<? super T> s) { static final class FromIterableDisposable<T> extends BasicQueueDisposable<T> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Iterator<? extends T> it; @@ -74,7 +74,7 @@ static final class FromIterableDisposable<T> extends BasicQueueDisposable<T> { boolean checkNext; FromIterableDisposable(Observer<? super T> actual, Iterator<? extends T> it) { - this.actual = actual; + this.downstream = actual; this.it = it; } @@ -91,11 +91,11 @@ void run() { v = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } - actual.onNext(v); + downstream.onNext(v); if (isDisposed()) { return; @@ -104,13 +104,13 @@ void run() { hasNext = it.hasNext(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } } while (hasNext); if (!isDisposed()) { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java index 3de1735670..27b4305c89 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java @@ -34,46 +34,46 @@ protected void subscribeActual(final Observer<? super T> o) { static final class PublisherSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final Observer<? super T> actual; - Subscription s; + final Observer<? super T> downstream; + Subscription upstream; PublisherSubscriber(Observer<? super T> o) { - this.actual = o; + this.downstream = o; } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } } @Override public void dispose() { - s.cancel(); - s = SubscriptionHelper.CANCELLED; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; } @Override public boolean isDisposed() { - return s == SubscriptionHelper.CANCELLED; + return upstream == SubscriptionHelper.CANCELLED; } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java index 063ad6d980..61f4891d1e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java @@ -35,26 +35,26 @@ public ObservableGenerate(Callable<S> stateSupplier, BiFunction<S, Emitter<T>, S } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { S state; try { state = stateSupplier.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - GeneratorDisposable<T, S> gd = new GeneratorDisposable<T, S>(s, generator, disposeState, state); - s.onSubscribe(gd); + GeneratorDisposable<T, S> gd = new GeneratorDisposable<T, S>(observer, generator, disposeState, state); + observer.onSubscribe(gd); gd.run(); } static final class GeneratorDisposable<T, S> implements Emitter<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final BiFunction<S, ? super Emitter<T>, S> generator; final Consumer<? super S> disposeState; @@ -69,7 +69,7 @@ static final class GeneratorDisposable<T, S> GeneratorDisposable(Observer<? super T> actual, BiFunction<S, ? super Emitter<T>, S> generator, Consumer<? super S> disposeState, S initialState) { - this.actual = actual; + this.downstream = actual; this.generator = generator; this.disposeState = disposeState; this.state = initialState; @@ -136,7 +136,6 @@ public boolean isDisposed() { return cancelled; } - @Override public void onNext(T t) { if (!terminate) { @@ -147,7 +146,7 @@ public void onNext(T t) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); } else { hasNext = true; - actual.onNext(t); + downstream.onNext(t); } } } @@ -162,7 +161,7 @@ public void onError(Throwable t) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } terminate = true; - actual.onError(t); + downstream.onError(t); } } @@ -170,7 +169,7 @@ public void onError(Throwable t) { public void onComplete() { if (!terminate) { terminate = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java index b2d60f492d..0c0390a107 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java @@ -52,7 +52,7 @@ public static final class GroupByObserver<T, K, V> extends AtomicInteger impleme private static final long serialVersionUID = -3688291656102519502L; - final Observer<? super GroupedObservable<K, V>> actual; + final Observer<? super GroupedObservable<K, V>> downstream; final Function<? super T, ? extends K> keySelector; final Function<? super T, ? extends V> valueSelector; final int bufferSize; @@ -61,12 +61,12 @@ public static final class GroupByObserver<T, K, V> extends AtomicInteger impleme static final Object NULL_KEY = new Object(); - Disposable s; + Disposable upstream; final AtomicBoolean cancelled = new AtomicBoolean(); public GroupByObserver(Observer<? super GroupedObservable<K, V>> actual, Function<? super T, ? extends K> keySelector, Function<? super T, ? extends V> valueSelector, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.keySelector = keySelector; this.valueSelector = valueSelector; this.bufferSize = bufferSize; @@ -76,10 +76,10 @@ public GroupByObserver(Observer<? super GroupedObservable<K, V>> actual, Functio } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -90,7 +90,7 @@ public void onNext(T t) { key = keySelector.apply(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } @@ -109,7 +109,7 @@ public void onNext(T t) { getAndIncrement(); - actual.onNext(group); + downstream.onNext(group); } V v; @@ -117,7 +117,7 @@ public void onNext(T t) { v = ObjectHelper.requireNonNull(valueSelector.apply(t), "The value supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } @@ -134,7 +134,7 @@ public void onError(Throwable t) { e.onError(t); } - actual.onError(t); + downstream.onError(t); } @Override @@ -146,7 +146,7 @@ public void onComplete() { e.onComplete(); } - actual.onComplete(); + downstream.onComplete(); } @Override @@ -155,7 +155,7 @@ public void dispose() { // but running groups still require new values if (cancelled.compareAndSet(false, true)) { if (decrementAndGet() == 0) { - s.dispose(); + upstream.dispose(); } } } @@ -169,7 +169,7 @@ public void cancel(K key) { Object mapKey = key != null ? key : NULL_KEY; groups.remove(mapKey); if (decrementAndGet() == 0) { - s.dispose(); + upstream.dispose(); } } } @@ -247,17 +247,17 @@ public boolean isDisposed() { } @Override - public void subscribe(Observer<? super T> s) { + public void subscribe(Observer<? super T> observer) { if (once.compareAndSet(false, true)) { - s.onSubscribe(this); - actual.lazySet(s); + observer.onSubscribe(this); + actual.lazySet(observer); if (cancelled.get()) { actual.lazySet(null); } else { drain(); } } else { - EmptyDisposable.error(new IllegalStateException("Only one Observer allowed!"), s); + EmptyDisposable.error(new IllegalStateException("Only one Observer allowed!"), observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java index a6bf21423f..23e39af881 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java @@ -56,12 +56,12 @@ public ObservableGroupJoin( } @Override - protected void subscribeActual(Observer<? super R> s) { + protected void subscribeActual(Observer<? super R> observer) { GroupJoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> parent = - new GroupJoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R>(s, leftEnd, rightEnd, resultSelector); + new GroupJoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R>(observer, leftEnd, rightEnd, resultSelector); - s.onSubscribe(parent); + observer.onSubscribe(parent); LeftRightObserver left = new LeftRightObserver(parent, true); parent.disposables.add(left); @@ -88,10 +88,9 @@ interface JoinSupport { static final class GroupJoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> extends AtomicInteger implements Disposable, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final SpscLinkedArrayQueue<Object> queue; @@ -130,7 +129,7 @@ static final class GroupJoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> Function<? super TLeft, ? extends ObservableSource<TLeftEnd>> leftEnd, Function<? super TRight, ? extends ObservableSource<TRightEnd>> rightEnd, BiFunction<? super TLeft, ? super Observable<TRight>, ? extends R> resultSelector) { - this.actual = actual; + this.downstream = actual; this.disposables = new CompositeDisposable(); this.queue = new SpscLinkedArrayQueue<Object>(bufferSize()); this.lefts = new LinkedHashMap<Integer, UnicastSubject<TRight>>(); @@ -191,7 +190,7 @@ void drain() { int missed = 1; SpscLinkedArrayQueue<Object> q = queue; - Observer<? super R> a = actual; + Observer<? super R> a = downstream; for (;;) { for (;;) { @@ -405,8 +404,8 @@ public boolean isDisposed() { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override @@ -456,8 +455,8 @@ public boolean isDisposed() { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java index f85b92c173..6685146fcb 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java @@ -36,45 +36,45 @@ protected void subscribeActual(Observer<? super T> o) { static final class HideDisposable<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; - Disposable d; + Disposable upstream; - HideDisposable(Observer<? super T> actual) { - this.actual = actual; + HideDisposable(Observer<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java index 58de57c635..b609cdee0c 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java @@ -28,18 +28,18 @@ public void subscribeActual(final Observer<? super T> t) { } static final class IgnoreObservable<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; - Disposable d; + Disposable upstream; IgnoreObservable(Observer<? super T> t) { - this.actual = t; + this.downstream = t; } @Override - public void onSubscribe(Disposable s) { - this.d = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + this.upstream = d; + downstream.onSubscribe(this); } @Override @@ -49,22 +49,22 @@ public void onNext(T v) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java index 792dd4f456..15b3789e18 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java @@ -37,18 +37,18 @@ public Observable<T> fuseToObservable() { } static final class IgnoreObservable<T> implements Observer<T>, Disposable { - final CompletableObserver actual; + final CompletableObserver downstream; - Disposable d; + Disposable upstream; IgnoreObservable(CompletableObserver t) { - this.actual = t; + this.downstream = t; } @Override - public void onSubscribe(Disposable s) { - this.d = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + this.upstream = d; + downstream.onSubscribe(this); } @Override @@ -58,22 +58,22 @@ public void onNext(T v) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java index b5fe5f48b5..733b18f0ee 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java @@ -17,11 +17,8 @@ import io.reactivex.*; import io.reactivex.functions.*; -import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.operators.single.SingleToObservable; +import io.reactivex.internal.functions.*; import io.reactivex.observables.ConnectableObservable; -import io.reactivex.plugins.RxJavaPlugins; /** * Helper utility class to support Observable with inner classes. @@ -86,7 +83,6 @@ public static <T, U> Function<T, ObservableSource<T>> itemDelay(final Function<? return new ItemDelayFunction<T, U>(itemDelay); } - static final class ObserverOnNext<T> implements Consumer<T> { final Observer<T> observer; @@ -202,24 +198,6 @@ public Object apply(Object t) throws Exception { } } - static final class RepeatWhenOuterHandler - implements Function<Observable<Notification<Object>>, ObservableSource<?>> { - private final Function<? super Observable<Object>, ? extends ObservableSource<?>> handler; - - RepeatWhenOuterHandler(Function<? super Observable<Object>, ? extends ObservableSource<?>> handler) { - this.handler = handler; - } - - @Override - public ObservableSource<?> apply(Observable<Notification<Object>> no) throws Exception { - return handler.apply(no.map(MapToInt.INSTANCE)); - } - } - - public static Function<Observable<Notification<Object>>, ObservableSource<?>> repeatWhenHandler(final Function<? super Observable<Object>, ? extends ObservableSource<?>> handler) { - return new RepeatWhenOuterHandler(handler); - } - public static <T> Callable<ConnectableObservable<T>> replayCallable(final Observable<T> parent) { return new ReplayCallable<T>(parent); } @@ -240,42 +218,6 @@ public static <T, R> Function<Observable<T>, ObservableSource<R>> replayFunction return new ReplayFunction<T, R>(selector, scheduler); } - enum ErrorMapperFilter implements Function<Notification<Object>, Throwable>, Predicate<Notification<Object>> { - INSTANCE; - - @Override - public Throwable apply(Notification<Object> t) throws Exception { - return t.getError(); - } - - @Override - public boolean test(Notification<Object> t) throws Exception { - return t.isOnError(); - } - } - - static final class RetryWhenInner - implements Function<Observable<Notification<Object>>, ObservableSource<?>> { - private final Function<? super Observable<Throwable>, ? extends ObservableSource<?>> handler; - - RetryWhenInner( - Function<? super Observable<Throwable>, ? extends ObservableSource<?>> handler) { - this.handler = handler; - } - - @Override - public ObservableSource<?> apply(Observable<Notification<Object>> no) throws Exception { - Observable<Throwable> map = no - .takeWhile(ErrorMapperFilter.INSTANCE) - .map(ErrorMapperFilter.INSTANCE); - return handler.apply(map); - } - } - - public static <T> Function<Observable<Notification<Object>>, ObservableSource<?>> retryWhenHandler(final Function<? super Observable<Throwable>, ? extends ObservableSource<?>> handler) { - return new RetryWhenInner(handler); - } - static final class ZipIterableFunction<T, R> implements Function<List<ObservableSource<? extends T>>, ObservableSource<? extends R>> { private final Function<? super Object[], ? extends R> zipper; @@ -294,37 +236,6 @@ public static <T, R> Function<List<ObservableSource<? extends T>>, ObservableSou return new ZipIterableFunction<T, R>(zipper); } - public static <T,R> Observable<R> switchMapSingle(Observable<T> source, final Function<? super T, ? extends SingleSource<? extends R>> mapper) { - return source.switchMap(convertSingleMapperToObservableMapper(mapper), 1); - } - - public static <T,R> Observable<R> switchMapSingleDelayError(Observable<T> source, - Function<? super T, ? extends SingleSource<? extends R>> mapper) { - return source.switchMapDelayError(convertSingleMapperToObservableMapper(mapper), 1); - } - - private static <T, R> Function<T, Observable<R>> convertSingleMapperToObservableMapper( - final Function<? super T, ? extends SingleSource<? extends R>> mapper) { - ObjectHelper.requireNonNull(mapper, "mapper is null"); - return new ObservableMapper<T,R>(mapper); - } - - static final class ObservableMapper<T,R> implements Function<T,Observable<R>> { - - final Function<? super T, ? extends SingleSource<? extends R>> mapper; - - ObservableMapper(Function<? super T, ? extends SingleSource<? extends R>> mapper) { - this.mapper = mapper; - } - - @Override - public Observable<R> apply(T t) throws Exception { - return RxJavaPlugins.onAssembly(new SingleToObservable<R>( - ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"))); - } - - } - static final class ReplayCallable<T> implements Callable<ConnectableObservable<T>> { private final Observable<T> parent; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java index 7607b62458..947a3941fa 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java @@ -36,9 +36,9 @@ public ObservableInterval(long initialDelay, long period, TimeUnit unit, Schedul } @Override - public void subscribeActual(Observer<? super Long> s) { - IntervalObserver is = new IntervalObserver(s); - s.onSubscribe(is); + public void subscribeActual(Observer<? super Long> observer) { + IntervalObserver is = new IntervalObserver(observer); + observer.onSubscribe(is); Scheduler sch = scheduler; @@ -56,15 +56,14 @@ static final class IntervalObserver extends AtomicReference<Disposable> implements Disposable, Runnable { - private static final long serialVersionUID = 346773832286157679L; - final Observer<? super Long> actual; + final Observer<? super Long> downstream; long count; - IntervalObserver(Observer<? super Long> actual) { - this.actual = actual; + IntervalObserver(Observer<? super Long> downstream) { + this.downstream = downstream; } @Override @@ -80,7 +79,7 @@ public boolean isDisposed() { @Override public void run() { if (get() != DisposableHelper.DISPOSED) { - actual.onNext(count++); + downstream.onNext(count++); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java index 3fd5396bfd..5d48d1d3d0 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java @@ -40,9 +40,9 @@ public ObservableIntervalRange(long start, long end, long initialDelay, long per } @Override - public void subscribeActual(Observer<? super Long> s) { - IntervalRangeObserver is = new IntervalRangeObserver(s, start, end); - s.onSubscribe(is); + public void subscribeActual(Observer<? super Long> observer) { + IntervalRangeObserver is = new IntervalRangeObserver(observer, start, end); + observer.onSubscribe(is); Scheduler sch = scheduler; @@ -60,16 +60,15 @@ static final class IntervalRangeObserver extends AtomicReference<Disposable> implements Disposable, Runnable { - private static final long serialVersionUID = 1891866368734007884L; - final Observer<? super Long> actual; + final Observer<? super Long> downstream; final long end; long count; IntervalRangeObserver(Observer<? super Long> actual, long start, long end) { - this.actual = actual; + this.downstream = actual; this.count = start; this.end = end; } @@ -88,11 +87,11 @@ public boolean isDisposed() { public void run() { if (!isDisposed()) { long c = count; - actual.onNext(c); + downstream.onNext(c); if (c == end) { DisposableHelper.dispose(this); - actual.onComplete(); + downstream.onComplete(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java index 2485c1cd2a..9a293ca7d5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java @@ -54,13 +54,13 @@ public ObservableJoin( } @Override - protected void subscribeActual(Observer<? super R> s) { + protected void subscribeActual(Observer<? super R> observer) { JoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> parent = new JoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R>( - s, leftEnd, rightEnd, resultSelector); + observer, leftEnd, rightEnd, resultSelector); - s.onSubscribe(parent); + observer.onSubscribe(parent); LeftRightObserver left = new LeftRightObserver(parent, true); parent.disposables.add(left); @@ -74,10 +74,9 @@ protected void subscribeActual(Observer<? super R> s) { static final class JoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> extends AtomicInteger implements Disposable, JoinSupport { - private static final long serialVersionUID = -6071216598687999801L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final SpscLinkedArrayQueue<Object> queue; @@ -115,7 +114,7 @@ static final class JoinDisposable<TLeft, TRight, TLeftEnd, TRightEnd, R> Function<? super TLeft, ? extends ObservableSource<TLeftEnd>> leftEnd, Function<? super TRight, ? extends ObservableSource<TRightEnd>> rightEnd, BiFunction<? super TLeft, ? super TRight, ? extends R> resultSelector) { - this.actual = actual; + this.downstream = actual; this.disposables = new CompositeDisposable(); this.queue = new SpscLinkedArrayQueue<Object>(bufferSize()); this.lefts = new LinkedHashMap<Integer, TLeft>(); @@ -171,7 +170,7 @@ void drain() { int missed = 1; SpscLinkedArrayQueue<Object> q = queue; - Observer<? super R> a = actual; + Observer<? super R> a = downstream; for (;;) { for (;;) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java index 653fef105b..0ae53fb597 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java @@ -29,9 +29,9 @@ public ObservableJust(final T value) { } @Override - protected void subscribeActual(Observer<? super T> s) { - ScalarDisposable<T> sd = new ScalarDisposable<T>(s, value); - s.onSubscribe(sd); + protected void subscribeActual(Observer<? super T> observer) { + ScalarDisposable<T> sd = new ScalarDisposable<T>(observer, value); + observer.onSubscribe(sd); sd.run(); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java index a5abffc1bd..d6b0da18d0 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java @@ -40,33 +40,33 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class LastObserver<T> implements Observer<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable s; + Disposable upstream; T item; - LastObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + LastObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - s.dispose(); - s = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return s == DisposableHelper.DISPOSED; + return upstream == DisposableHelper.DISPOSED; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -77,20 +77,20 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - s = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; item = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - s = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; T v = item; if (v != null) { item = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java index 324bccc04d..b1355f2ca5 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java @@ -45,36 +45,36 @@ protected void subscribeActual(SingleObserver<? super T> observer) { static final class LastObserver<T> implements Observer<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final T defaultItem; - Disposable s; + Disposable upstream; T item; LastObserver(SingleObserver<? super T> actual, T defaultItem) { - this.actual = actual; + this.downstream = actual; this.defaultItem = defaultItem; } @Override public void dispose() { - s.dispose(); - s = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override public boolean isDisposed() { - return s == DisposableHelper.DISPOSED; + return upstream == DisposableHelper.DISPOSED; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -85,24 +85,24 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - s = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; item = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - s = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; T v = item; if (v != null) { item = null; - actual.onSuccess(v); + downstream.onSuccess(v); } else { v = defaultItem; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java index ca9c36a697..8cdd918564 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java @@ -37,10 +37,10 @@ public ObservableLift(ObservableSource<T> source, ObservableOperator<? extends R } @Override - public void subscribeActual(Observer<? super R> s) { - Observer<? super T> observer; + public void subscribeActual(Observer<? super R> observer) { + Observer<? super T> liftedObserver; try { - observer = ObjectHelper.requireNonNull(operator.apply(s), "Operator " + operator + " returned a null Observer"); + liftedObserver = ObjectHelper.requireNonNull(operator.apply(observer), "Operator " + operator + " returned a null Observer"); } catch (NullPointerException e) { // NOPMD throw e; } catch (Throwable e) { @@ -54,6 +54,6 @@ public void subscribeActual(Observer<? super R> s) { throw npe; } - source.subscribe(observer); + source.subscribe(liftedObserver); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java index caed356c01..475963ce85 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - package io.reactivex.internal.operators.observable; import io.reactivex.*; @@ -33,7 +32,6 @@ public void subscribeActual(Observer<? super U> t) { source.subscribe(new MapObserver<T, U>(t, function)); } - static final class MapObserver<T, U> extends BasicFuseableObserver<T, U> { final Function<? super T, ? extends U> mapper; @@ -49,7 +47,7 @@ public void onNext(T t) { } if (sourceMode != NONE) { - actual.onNext(null); + downstream.onNext(null); return; } @@ -61,7 +59,7 @@ public void onNext(T t) { fail(ex); return; } - actual.onNext(v); + downstream.onNext(v); } @Override @@ -72,7 +70,7 @@ public int requestFusion(int mode) { @Nullable @Override public U poll() throws Exception { - T t = qs.poll(); + T t = qd.poll(); return t != null ? ObjectHelper.<U>requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java index 1fb5f94451..f4418e9927 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java @@ -46,43 +46,41 @@ public void subscribeActual(Observer<? super ObservableSource<? extends R>> t) { static final class MapNotificationObserver<T, R> implements Observer<T>, Disposable { - final Observer<? super ObservableSource<? extends R>> actual; + final Observer<? super ObservableSource<? extends R>> downstream; final Function<? super T, ? extends ObservableSource<? extends R>> onNextMapper; final Function<? super Throwable, ? extends ObservableSource<? extends R>> onErrorMapper; final Callable<? extends ObservableSource<? extends R>> onCompleteSupplier; - Disposable s; + Disposable upstream; MapNotificationObserver(Observer<? super ObservableSource<? extends R>> actual, Function<? super T, ? extends ObservableSource<? extends R>> onNextMapper, Function<? super Throwable, ? extends ObservableSource<? extends R>> onErrorMapper, Callable<? extends ObservableSource<? extends R>> onCompleteSupplier) { - this.actual = actual; + this.downstream = actual; this.onNextMapper = onNextMapper; this.onErrorMapper = onErrorMapper; this.onCompleteSupplier = onCompleteSupplier; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { ObservableSource<? extends R> p; @@ -91,11 +89,11 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(onNextMapper.apply(t), "The onNext ObservableSource returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } - actual.onNext(p); + downstream.onNext(p); } @Override @@ -106,12 +104,12 @@ public void onError(Throwable t) { p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError ObservableSource returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } - actual.onNext(p); - actual.onComplete(); + downstream.onNext(p); + downstream.onComplete(); } @Override @@ -122,12 +120,12 @@ public void onComplete() { p = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onComplete ObservableSource returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } - actual.onNext(p); - actual.onComplete(); + downstream.onNext(p); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java index f2db8cc3df..9cfe82b16a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java @@ -19,7 +19,6 @@ public final class ObservableMaterialize<T> extends AbstractObservableWithUpstream<T, Notification<T>> { - public ObservableMaterialize(ObservableSource<T> source) { super(source); } @@ -30,51 +29,50 @@ public void subscribeActual(Observer<? super Notification<T>> t) { } static final class MaterializeObserver<T> implements Observer<T>, Disposable { - final Observer<? super Notification<T>> actual; + final Observer<? super Notification<T>> downstream; - Disposable s; + Disposable upstream; - MaterializeObserver(Observer<? super Notification<T>> actual) { - this.actual = actual; + MaterializeObserver(Observer<? super Notification<T>> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override public void onNext(T t) { - actual.onNext(Notification.createOnNext(t)); + downstream.onNext(Notification.createOnNext(t)); } @Override public void onError(Throwable t) { Notification<T> v = Notification.createOnError(t); - actual.onNext(v); - actual.onComplete(); + downstream.onNext(v); + downstream.onComplete(); } @Override public void onComplete() { Notification<T> v = Notification.createOnComplete(); - actual.onNext(v); - actual.onComplete(); + downstream.onNext(v); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java new file mode 100644 index 0000000000..3b9e649062 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.*; + +/** + * Merges an Observable and a Completable by emitting the items of the Observable and waiting until + * both the Observable and Completable complete normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithCompletable<T> extends AbstractObservableWithUpstream<T, T> { + + final CompletableSource other; + + public ObservableMergeWithCompletable(Observable<T> source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + MergeWithObserver<T> parent = new MergeWithObserver<T>(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver<T> extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer<? super T> downstream; + + final AtomicReference<Disposable> mainDisposable; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + volatile boolean mainDone; + + volatile boolean otherDone; + + MergeWithObserver(Observer<? super T> downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference<Disposable>(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable ex) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, ex, this, error); + } + + @Override + public void onComplete() { + mainDone = true; + if (otherDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + } + + void otherError(Throwable ex) { + DisposableHelper.dispose(mainDisposable); + HalfSerializer.onError(downstream, ex, this, error); + } + + void otherComplete() { + otherDone = true; + if (mainDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + static final class OtherObserver extends AtomicReference<Disposable> + implements CompletableObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver<?> parent; + + OtherObserver(MergeWithObserver<?> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java new file mode 100644 index 0000000000..e7caad3b21 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java @@ -0,0 +1,266 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithMaybe<T> extends AbstractObservableWithUpstream<T, T> { + + final MaybeSource<? extends T> other; + + public ObservableMergeWithMaybe(Observable<T> source, MaybeSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + MergeWithObserver<T> parent = new MergeWithObserver<T>(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver<T> extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer<? super T> downstream; + + final AtomicReference<Disposable> mainDisposable; + + final OtherObserver<T> otherObserver; + + final AtomicThrowable error; + + volatile SimplePlainQueue<T> queue; + + T singleItem; + + volatile boolean disposed; + + volatile boolean mainDone; + + volatile int otherState; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Observer<? super T> downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference<Disposable>(); + this.otherObserver = new OtherObserver<T>(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + downstream.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + disposed = true; + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(mainDisposable); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void otherComplete() { + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + drain(); + } + + SimplePlainQueue<T> getOrCreateQueue() { + SimplePlainQueue<T> q = queue; + if (q == null) { + q = new SpscLinkedArrayQueue<T>(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Observer<? super T> actual = this.downstream; + int missed = 1; + for (;;) { + + for (;;) { + if (disposed) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver<T> extends AtomicReference<Disposable> + implements MaybeObserver<T> { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver<T> parent; + + OtherObserver(MergeWithObserver<T> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java new file mode 100644 index 0000000000..7332a29a25 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java @@ -0,0 +1,257 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Single by emitting the items of the Observable and the success + * value of the Single and waiting until both the Observable and Single terminate normally. + * <p>History: 2.1.10 - experimental + * @param <T> the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithSingle<T> extends AbstractObservableWithUpstream<T, T> { + + final SingleSource<? extends T> other; + + public ObservableMergeWithSingle(Observable<T> source, SingleSource<? extends T> other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + MergeWithObserver<T> parent = new MergeWithObserver<T>(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver<T> extends AtomicInteger + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer<? super T> downstream; + + final AtomicReference<Disposable> mainDisposable; + + final OtherObserver<T> otherObserver; + + final AtomicThrowable error; + + volatile SimplePlainQueue<T> queue; + + T singleItem; + + volatile boolean disposed; + + volatile boolean mainDone; + + volatile int otherState; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Observer<? super T> downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference<Disposable>(); + this.otherObserver = new OtherObserver<T>(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + downstream.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue<T> q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + disposed = true; + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(mainDisposable); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + SimplePlainQueue<T> getOrCreateQueue() { + SimplePlainQueue<T> q = queue; + if (q == null) { + q = new SpscLinkedArrayQueue<T>(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Observer<? super T> actual = this.downstream; + int missed = 1; + for (;;) { + + for (;;) { + if (disposed) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + } + + boolean d = mainDone; + SimplePlainQueue<T> q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver<T> extends AtomicReference<Disposable> + implements SingleObserver<T> { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver<T> parent; + + OtherObserver(MergeWithObserver<T> parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java index b54b671a86..56de30041e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java @@ -50,38 +50,38 @@ static final class ObserveOnObserver<T> extends BasicIntQueueDisposable<T> implements Observer<T>, Runnable { private static final long serialVersionUID = 6576896619930983584L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final Scheduler.Worker worker; final boolean delayError; final int bufferSize; SimpleQueue<T> queue; - Disposable s; + Disposable upstream; Throwable error; volatile boolean done; - volatile boolean cancelled; + volatile boolean disposed; int sourceMode; boolean outputFused; ObserveOnObserver(Observer<? super T> actual, Scheduler.Worker worker, boolean delayError, int bufferSize) { - this.actual = actual; + this.downstream = actual; this.worker = worker; this.delayError = delayError; this.bufferSize = bufferSize; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - if (s instanceof QueueDisposable) { + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") - QueueDisposable<T> qd = (QueueDisposable<T>) s; + QueueDisposable<T> qd = (QueueDisposable<T>) d; int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); @@ -89,21 +89,21 @@ public void onSubscribe(Disposable s) { sourceMode = m; queue = qd; done = true; - actual.onSubscribe(this); + downstream.onSubscribe(this); schedule(); return; } if (m == QueueDisposable.ASYNC) { sourceMode = m; queue = qd; - actual.onSubscribe(this); + downstream.onSubscribe(this); return; } } queue = new SpscLinkedArrayQueue<T>(bufferSize); - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -141,11 +141,11 @@ public void onComplete() { @Override public void dispose() { - if (!cancelled) { - cancelled = true; - s.dispose(); + if (!disposed) { + disposed = true; + upstream.dispose(); worker.dispose(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -153,7 +153,7 @@ public void dispose() { @Override public boolean isDisposed() { - return cancelled; + return disposed; } void schedule() { @@ -166,7 +166,7 @@ void drainNormal() { int missed = 1; final SimpleQueue<T> q = queue; - final Observer<? super T> a = actual; + final Observer<? super T> a = downstream; for (;;) { if (checkTerminated(done, q.isEmpty(), a)) { @@ -181,7 +181,8 @@ void drainNormal() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.dispose(); + disposed = true; + upstream.dispose(); q.clear(); a.onError(ex); worker.dispose(); @@ -211,7 +212,7 @@ void drainFused() { int missed = 1; for (;;) { - if (cancelled) { + if (disposed) { return; } @@ -219,19 +220,21 @@ void drainFused() { Throwable ex = error; if (!delayError && d && ex != null) { - actual.onError(error); + disposed = true; + downstream.onError(error); worker.dispose(); return; } - actual.onNext(null); + downstream.onNext(null); if (d) { + disposed = true; ex = error; if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onComplete(); + downstream.onComplete(); } worker.dispose(); return; @@ -254,7 +257,7 @@ public void run() { } boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) { - if (cancelled) { + if (disposed) { queue.clear(); return true; } @@ -262,6 +265,7 @@ boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) { Throwable e = error; if (delayError) { if (empty) { + disposed = true; if (e != null) { a.onError(e); } else { @@ -272,12 +276,14 @@ boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) { } } else { if (e != null) { + disposed = true; queue.clear(); a.onError(e); worker.dispose(); return true; } else if (empty) { + disposed = true; a.onComplete(); worker.dispose(); return true; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java index 93df2a0548..649831d59f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java @@ -39,7 +39,7 @@ public void subscribeActual(Observer<? super T> t) { } static final class OnErrorNextObserver<T> implements Observer<T> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Function<? super Throwable, ? extends ObservableSource<? extends T>> nextSupplier; final boolean allowFatal; final SequentialDisposable arbiter; @@ -49,15 +49,15 @@ static final class OnErrorNextObserver<T> implements Observer<T> { boolean done; OnErrorNextObserver(Observer<? super T> actual, Function<? super Throwable, ? extends ObservableSource<? extends T>> nextSupplier, boolean allowFatal) { - this.actual = actual; + this.downstream = actual; this.nextSupplier = nextSupplier; this.allowFatal = allowFatal; this.arbiter = new SequentialDisposable(); } @Override - public void onSubscribe(Disposable s) { - arbiter.replace(s); + public void onSubscribe(Disposable d) { + arbiter.replace(d); } @Override @@ -65,7 +65,7 @@ public void onNext(T t) { if (done) { return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -75,13 +75,13 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } - actual.onError(t); + downstream.onError(t); return; } once = true; if (allowFatal && !(t instanceof Exception)) { - actual.onError(t); + downstream.onError(t); return; } @@ -91,14 +91,14 @@ public void onError(Throwable t) { p = nextSupplier.apply(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (p == null) { NullPointerException npe = new NullPointerException("Observable is null"); npe.initCause(t); - actual.onError(npe); + downstream.onError(npe); return; } @@ -112,7 +112,7 @@ public void onComplete() { } done = true; once = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java index 087aa47b24..b91e364856 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java @@ -32,38 +32,37 @@ public void subscribeActual(Observer<? super T> t) { } static final class OnErrorReturnObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Function<? super Throwable, ? extends T> valueSupplier; - Disposable s; + Disposable upstream; OnErrorReturnObserver(Observer<? super T> actual, Function<? super Throwable, ? extends T> valueSupplier) { - this.actual = actual; + this.downstream = actual; this.valueSupplier = valueSupplier; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override @@ -73,24 +72,24 @@ public void onError(Throwable t) { v = valueSupplier.apply(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (v == null) { NullPointerException e = new NullPointerException("The supplied value is null"); e.initCause(t); - actual.onError(e); + downstream.onError(e); return; } - actual.onNext(v); - actual.onComplete(); + downstream.onNext(v); + downstream.onComplete(); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java index b8c5206c2b..04b90506c3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java @@ -30,7 +30,8 @@ * manner. * @param <T> the value type */ -public final class ObservablePublish<T> extends ConnectableObservable<T> implements HasUpstreamObservableSource<T> { +public final class ObservablePublish<T> extends ConnectableObservable<T> +implements HasUpstreamObservableSource<T>, ObservablePublishClassic<T> { /** The source observable. */ final ObservableSource<T> source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -63,6 +64,11 @@ public ObservableSource<T> source() { return source; } + @Override + public ObservableSource<T> publishSource() { + return source; + } + @Override protected void subscribeActual(Observer<? super T> observer) { onSubscribe.subscribe(observer); @@ -136,7 +142,7 @@ static final class PublishObserver<T> */ final AtomicBoolean shouldConnect; - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); @SuppressWarnings("unchecked") PublishObserver(AtomicReference<PublishObserver<T>> current) { @@ -148,13 +154,11 @@ static final class PublishObserver<T> @SuppressWarnings("unchecked") @Override public void dispose() { - if (observers.get() != TERMINATED) { - InnerDisposable[] ps = observers.getAndSet(TERMINATED); - if (ps != TERMINATED) { - current.compareAndSet(PublishObserver.this, null); + InnerDisposable[] ps = observers.getAndSet(TERMINATED); + if (ps != TERMINATED) { + current.compareAndSet(PublishObserver.this, null); - DisposableHelper.dispose(s); - } + DisposableHelper.dispose(upstream); } } @@ -164,8 +168,8 @@ public boolean isDisposed() { } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override @@ -174,6 +178,7 @@ public void onNext(T t) { inner.child.onNext(t); } } + @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { @@ -187,6 +192,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @SuppressWarnings("unchecked") @Override public void onComplete() { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java new file mode 100644 index 0000000000..a9e62babde --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java @@ -0,0 +1,282 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; + +/** + * Shares a single underlying connection to the upstream ObservableSource + * and multicasts events to all subscribed observers until the upstream + * completes or the connection is disposed. + * <p> + * The difference to ObservablePublish is that when the upstream terminates, + * late observers will receive that terminal event until the connection is + * disposed and the ConnectableObservable is reset to its fresh state. + * + * @param <T> the element type + * @since 2.2.10 + */ +public final class ObservablePublishAlt<T> extends ConnectableObservable<T> +implements HasUpstreamObservableSource<T>, ResettableConnectable { + + final ObservableSource<T> source; + + final AtomicReference<PublishConnection<T>> current; + + public ObservablePublishAlt(ObservableSource<T> source) { + this.source = source; + this.current = new AtomicReference<PublishConnection<T>>(); + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + boolean doConnect = false; + PublishConnection<T> conn; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection<T> fresh = new PublishConnection<T>(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + PublishConnection<T> conn; + + for (;;) { + conn = current.get(); + // we don't create a fresh connection if the current is terminated + if (conn == null) { + PublishConnection<T> fresh = new PublishConnection<T>(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + break; + } + + InnerDisposable<T> inner = new InnerDisposable<T>(observer, conn); + observer.onSubscribe(inner); + if (conn.add(inner)) { + if (inner.isDisposed()) { + conn.remove(inner); + } + return; + } + // Late observers will be simply terminated + Throwable error = conn.error; + if (error != null) { + observer.onError(error); + } else { + observer.onComplete(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection<T>)connection, null); + } + + @Override + public ObservableSource<T> source() { + return source; + } + + static final class PublishConnection<T> + extends AtomicReference<InnerDisposable<T>[]> + implements Observer<T>, Disposable { + + private static final long serialVersionUID = -3251430252873581268L; + + final AtomicBoolean connect; + + final AtomicReference<PublishConnection<T>> current; + + final AtomicReference<Disposable> upstream; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] EMPTY = new InnerDisposable[0]; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] TERMINATED = new InnerDisposable[0]; + + Throwable error; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference<PublishConnection<T>> current) { + this.connect = new AtomicBoolean(); + this.current = current; + this.upstream = new AtomicReference<Disposable>(); + lazySet(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + getAndSet(TERMINATED); + current.compareAndSet(this, null); + DisposableHelper.dispose(upstream); + } + + @Override + public boolean isDisposed() { + return get() == TERMINATED; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + for (InnerDisposable<T> inner : get()) { + inner.downstream.onNext(t); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onError(Throwable e) { + error = e; + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable<T> inner : getAndSet(TERMINATED)) { + inner.downstream.onError(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onComplete() { + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable<T> inner : getAndSet(TERMINATED)) { + inner.downstream.onComplete(); + } + } + + public boolean add(InnerDisposable<T> inner) { + for (;;) { + InnerDisposable<T>[] a = get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + InnerDisposable<T>[] b = new InnerDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + public void remove(InnerDisposable<T> inner) { + for (;;) { + InnerDisposable<T>[] a = get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + InnerDisposable<T>[] b = EMPTY; + if (n != 1) { + b = new InnerDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (compareAndSet(a, b)) { + return; + } + } + } + } + + /** + * Intercepts the dispose signal from the downstream and + * removes itself from the connection's observers array + * at most once. + * @param <T> the element type + */ + static final class InnerDisposable<T> + extends AtomicReference<PublishConnection<T>> + implements Disposable { + + private static final long serialVersionUID = 7463222674719692880L; + + final Observer<? super T> downstream; + + InnerDisposable(Observer<? super T> downstream, PublishConnection<T> parent) { + this.downstream = downstream; + lazySet(parent); + } + + @Override + public void dispose() { + PublishConnection<T> p = getAndSet(null); + if (p != null) { + p.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java new file mode 100644 index 0000000000..b2bfe87f71 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import io.reactivex.ObservableSource; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + * <p> + * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param <T> the element type of the sequence + * @since 2.2.10 + */ +public interface ObservablePublishClassic<T> { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + ObservableSource<T> publishSource(); +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java index 05abfd6181..17824fb4ca 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java @@ -95,49 +95,49 @@ static final class TargetObserver<T, R> extends AtomicReference<Disposable> implements Observer<R>, Disposable { private static final long serialVersionUID = 854110278590336484L; - final Observer<? super R> actual; + final Observer<? super R> downstream; - Disposable d; + Disposable upstream; - TargetObserver(Observer<? super R> actual) { - this.actual = actual; + TargetObserver(Observer<? super R> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(R value) { - actual.onNext(value); + downstream.onNext(value); } @Override public void onError(Throwable e) { DisposableHelper.dispose(this); - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { DisposableHelper.dispose(this); - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); DisposableHelper.dispose(this); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java index 2718ef6c8e..19cb08cc21 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java @@ -40,7 +40,7 @@ static final class RangeDisposable private static final long serialVersionUID = 396518478098735504L; - final Observer<? super Integer> actual; + final Observer<? super Integer> downstream; final long end; @@ -49,7 +49,7 @@ static final class RangeDisposable boolean fused; RangeDisposable(Observer<? super Integer> actual, long start, long end) { - this.actual = actual; + this.downstream = actual; this.index = start; this.end = end; } @@ -58,7 +58,7 @@ void run() { if (fused) { return; } - Observer<? super Integer> actual = this.actual; + Observer<? super Integer> actual = this.downstream; long e = end; for (long i = index; i != e && get() == 0; i++) { actual.onNext((int)i); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java index 851a5ecea3..55d23d28f1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java @@ -37,7 +37,7 @@ static final class RangeDisposable private static final long serialVersionUID = 396518478098735504L; - final Observer<? super Long> actual; + final Observer<? super Long> downstream; final long end; @@ -46,7 +46,7 @@ static final class RangeDisposable boolean fused; RangeDisposable(Observer<? super Long> actual, long start, long end) { - this.actual = actual; + this.downstream = actual; this.index = start; this.end = end; } @@ -55,7 +55,7 @@ void run() { if (fused) { return; } - Observer<? super Long> actual = this.actual; + Observer<? super Long> actual = this.downstream; long e = end; for (long i = index; i != e && get() == 0; i++) { actual.onNext(i); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java index 0215e73fb4..63d0e6c2d1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java @@ -45,7 +45,7 @@ protected void subscribeActual(MaybeObserver<? super T> observer) { static final class ReduceObserver<T> implements Observer<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; final BiFunction<T, T, T> reducer; @@ -53,19 +53,19 @@ static final class ReduceObserver<T> implements Observer<T>, Disposable { T value; - Disposable d; + Disposable upstream; ReduceObserver(MaybeObserver<? super T> observer, BiFunction<T, T, T> reducer) { - this.actual = observer; + this.downstream = observer; this.reducer = reducer; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -81,7 +81,7 @@ public void onNext(T value) { this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); } } @@ -96,7 +96,7 @@ public void onError(Throwable e) { } done = true; value = null; - actual.onError(e); + downstream.onError(e); } @Override @@ -108,20 +108,20 @@ public void onComplete() { T v = value; value = null; if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java index bbf31f4057..6a11b91631 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java @@ -49,26 +49,26 @@ protected void subscribeActual(SingleObserver<? super R> observer) { static final class ReduceSeedObserver<T, R> implements Observer<T>, Disposable { - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; final BiFunction<R, ? super T, R> reducer; R value; - Disposable d; + Disposable upstream; ReduceSeedObserver(SingleObserver<? super R> actual, BiFunction<R, ? super T, R> reducer, R value) { - this.actual = actual; + this.downstream = actual; this.value = value; this.reducer = reducer; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -80,7 +80,7 @@ public void onNext(T value) { this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - d.dispose(); + upstream.dispose(); onError(ex); } } @@ -89,9 +89,9 @@ public void onNext(T value) { @Override public void onError(Throwable e) { R v = value; - value = null; if (v != null) { - actual.onError(e); + value = null; + downstream.onError(e); } else { RxJavaPlugins.onError(e); } @@ -100,20 +100,20 @@ public void onError(Throwable e) { @Override public void onComplete() { R v = value; - value = null; if (v != null) { - actual.onSuccess(v); + value = null; + downstream.onSuccess(v); } } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java index 58a3141b4c..27e633c664 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java @@ -13,14 +13,15 @@ package io.reactivex.internal.operators.observable; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.*; -import java.util.concurrent.locks.ReentrantLock; import io.reactivex.*; -import io.reactivex.disposables.*; +import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.disposables.*; import io.reactivex.observables.ConnectableObservable; +import io.reactivex.plugins.RxJavaPlugins; /** * Returns an observable sequence that stays connected to the source as long as @@ -29,201 +30,240 @@ * @param <T> * the value type */ -public final class ObservableRefCount<T> extends AbstractObservableWithUpstream<T, T> { +public final class ObservableRefCount<T> extends Observable<T> { - final ConnectableObservable<? extends T> source; + final ConnectableObservable<T> source; - volatile CompositeDisposable baseDisposable = new CompositeDisposable(); + final int n; - final AtomicInteger subscriptionCount = new AtomicInteger(); + final long timeout; - /** - * Use this lock for every subscription and disconnect action. - */ - final ReentrantLock lock = new ReentrantLock(); + final TimeUnit unit; + + final Scheduler scheduler; + + RefConnection connection; - /** - * Constructor. - * - * @param source - * observable to apply ref count to - */ public ObservableRefCount(ConnectableObservable<T> source) { - super(source); + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); + } + + public ObservableRefCount(ConnectableObservable<T> source, int n, long timeout, TimeUnit unit, + Scheduler scheduler) { this.source = source; + this.n = n; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; } @Override - public void subscribeActual(final Observer<? super T> subscriber) { - - lock.lock(); - if (subscriptionCount.incrementAndGet() == 1) { - - final AtomicBoolean writeLocked = new AtomicBoolean(true); - - try { - // need to use this overload of connect to ensure that - // baseDisposable is set in the case that source is a - // synchronous Observable - source.connect(onSubscribe(subscriber, writeLocked)); - } finally { - // need to cover the case where the source is subscribed to - // outside of this class thus preventing the Consumer passed - // to source.connect above being called - if (writeLocked.get()) { - // Consumer passed to source.connect was not called - lock.unlock(); - } + protected void subscribeActual(Observer<? super T> observer) { + + RefConnection conn; + + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null) { + conn = new RefConnection(this); + connection = conn; + } + + long c = conn.subscriberCount; + if (c == 0L && conn.timer != null) { + conn.timer.dispose(); } - } else { - try { - // ready to subscribe to source so do it - doSubscribe(subscriber, baseDisposable); - } finally { - // release the read lock - lock.unlock(); + conn.subscriberCount = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; } } + source.subscribe(new RefCountObserver<T>(observer, this, conn)); + + if (connect) { + source.connect(conn); + } } - private Consumer<Disposable> onSubscribe(final Observer<? super T> observer, - final AtomicBoolean writeLocked) { - return new DisposeConsumer(observer, writeLocked); + void cancel(RefConnection rc) { + SequentialDisposable sd; + synchronized (this) { + if (connection == null || connection != rc) { + return; + } + long c = rc.subscriberCount - 1; + rc.subscriberCount = c; + if (c != 0L || !rc.connected) { + return; + } + if (timeout == 0L) { + timeout(rc); + return; + } + sd = new SequentialDisposable(); + rc.timer = sd; + } + + sd.replace(scheduler.scheduleDirect(rc, timeout, unit)); } - void doSubscribe(final Observer<? super T> observer, final CompositeDisposable currentBase) { - // handle disposing from the base CompositeDisposable - Disposable d = disconnect(currentBase); + void terminated(RefConnection rc) { + synchronized (this) { + if (source instanceof ObservablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); + } + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } + } + } + } + } - ConnectionObserver s = new ConnectionObserver(observer, currentBase, d); - observer.onSubscribe(s); + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; + } + } - source.subscribe(s); + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); + } } - private Disposable disconnect(final CompositeDisposable current) { - return Disposables.fromRunnable(new DisposeTask(current)); + void timeout(RefConnection rc) { + synchronized (this) { + if (rc.subscriberCount == 0 && rc == connection) { + connection = null; + Disposable connectionObject = rc.get(); + DisposableHelper.dispose(rc); + + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); + } + } + } + } } - final class ConnectionObserver - extends AtomicReference<Disposable> - implements Observer<T>, Disposable { + static final class RefConnection extends AtomicReference<Disposable> + implements Runnable, Consumer<Disposable> { - private static final long serialVersionUID = 3813126992133394324L; + private static final long serialVersionUID = -4552101107598366241L; - final Observer<? super T> subscriber; - final CompositeDisposable currentBase; - final Disposable resource; + final ObservableRefCount<?> parent; - ConnectionObserver(Observer<? super T> subscriber, - CompositeDisposable currentBase, Disposable resource) { - this.subscriber = subscriber; - this.currentBase = currentBase; - this.resource = resource; - } + Disposable timer; - @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); - } + long subscriberCount; - @Override - public void onError(Throwable e) { - cleanup(); - subscriber.onError(e); + boolean connected; + + boolean disconnectedEarly; + + RefConnection(ObservableRefCount<?> parent) { + this.parent = parent; } @Override - public void onNext(T t) { - subscriber.onNext(t); + public void run() { + parent.timeout(this); } @Override - public void onComplete() { - cleanup(); - subscriber.onComplete(); + public void accept(Disposable t) throws Exception { + DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); + } + } } + } - @Override - public void dispose() { - DisposableHelper.dispose(this); - resource.dispose(); + static final class RefCountObserver<T> + extends AtomicBoolean implements Observer<T>, Disposable { + + private static final long serialVersionUID = -7419642935409022375L; + + final Observer<? super T> downstream; + + final ObservableRefCount<T> parent; + + final RefConnection connection; + + Disposable upstream; + + RefCountObserver(Observer<? super T> downstream, ObservableRefCount<T> parent, RefConnection connection) { + this.downstream = downstream; + this.parent = parent; + this.connection = connection; } @Override - public boolean isDisposed() { - return DisposableHelper.isDisposed(get()); + public void onNext(T t) { + downstream.onNext(t); } - void cleanup() { - // on error or completion we need to dispose the base CompositeDisposable - // and set the subscriptionCount to 0 - lock.lock(); - try { - if (baseDisposable == currentBase) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); - } - - baseDisposable.dispose(); - baseDisposable = new CompositeDisposable(); - subscriptionCount.set(0); - } - } finally { - lock.unlock(); + @Override + public void onError(Throwable t) { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); } } - } - - final class DisposeConsumer implements Consumer<Disposable> { - private final Observer<? super T> observer; - private final AtomicBoolean writeLocked; - DisposeConsumer(Observer<? super T> observer, AtomicBoolean writeLocked) { - this.observer = observer; - this.writeLocked = writeLocked; + @Override + public void onComplete() { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onComplete(); + } } @Override - public void accept(Disposable subscription) { - try { - baseDisposable.add(subscription); - // ready to subscribe to source so do it - doSubscribe(observer, baseDisposable); - } finally { - // release the write lock - lock.unlock(); - writeLocked.set(false); + public void dispose() { + upstream.dispose(); + if (compareAndSet(false, true)) { + parent.cancel(connection); } } - } - - final class DisposeTask implements Runnable { - private final CompositeDisposable current; - DisposeTask(CompositeDisposable current) { - this.current = current; + @Override + public boolean isDisposed() { + return upstream.isDisposed(); } @Override - public void run() { - lock.lock(); - try { - if (baseDisposable == current) { - if (subscriptionCount.decrementAndGet() == 0) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); - } - - baseDisposable.dispose(); - // need a new baseDisposable because once - // disposed stays that way - baseDisposable = new CompositeDisposable(); - } - } - } finally { - lock.unlock(); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java index f3dce65e80..a42a8e85f6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java @@ -27,11 +27,11 @@ public ObservableRepeat(Observable<T> source, long count) { } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { SequentialDisposable sd = new SequentialDisposable(); - s.onSubscribe(sd); + observer.onSubscribe(sd); - RepeatObserver<T> rs = new RepeatObserver<T>(s, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sd, source); + RepeatObserver<T> rs = new RepeatObserver<T>(observer, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sd, source); rs.subscribeNext(); } @@ -39,29 +39,30 @@ static final class RepeatObserver<T> extends AtomicInteger implements Observer<T private static final long serialVersionUID = -7098360935104053232L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final SequentialDisposable sd; final ObservableSource<? extends T> source; long remaining; RepeatObserver(Observer<? super T> actual, long count, SequentialDisposable sd, ObservableSource<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.sd = sd; this.source = source; this.remaining = count; } @Override - public void onSubscribe(Disposable s) { - sd.replace(s); + public void onSubscribe(Disposable d) { + sd.replace(d); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } + @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -73,7 +74,7 @@ public void onComplete() { if (r != 0L) { subscribeNext(); } else { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java index 6f97906ece..485eb1f51f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java @@ -29,11 +29,11 @@ public ObservableRepeatUntil(Observable<T> source, BooleanSupplier until) { } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { SequentialDisposable sd = new SequentialDisposable(); - s.onSubscribe(sd); + observer.onSubscribe(sd); - RepeatUntilObserver<T> rs = new RepeatUntilObserver<T>(s, until, sd, source); + RepeatUntilObserver<T> rs = new RepeatUntilObserver<T>(observer, until, sd, source); rs.subscribeNext(); } @@ -41,29 +41,30 @@ static final class RepeatUntilObserver<T> extends AtomicInteger implements Obser private static final long serialVersionUID = -7098360935104053232L; - final Observer<? super T> actual; - final SequentialDisposable sd; + final Observer<? super T> downstream; + final SequentialDisposable upstream; final ObservableSource<? extends T> source; final BooleanSupplier stop; RepeatUntilObserver(Observer<? super T> actual, BooleanSupplier until, SequentialDisposable sd, ObservableSource<? extends T> source) { - this.actual = actual; - this.sd = sd; + this.downstream = actual; + this.upstream = sd; this.source = source; this.stop = until; } @Override - public void onSubscribe(Disposable s) { - sd.replace(s); + public void onSubscribe(Disposable d) { + upstream.replace(d); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } + @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -73,11 +74,11 @@ public void onComplete() { b = stop.getAsBoolean(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } if (b) { - actual.onComplete(); + downstream.onComplete(); } else { subscribeNext(); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java index 36143ebc00..24725997d1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java @@ -64,7 +64,7 @@ static final class RepeatWhenObserver<T> extends AtomicInteger implements Observ private static final long serialVersionUID = 802743776666017014L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final AtomicInteger wip; @@ -74,52 +74,53 @@ static final class RepeatWhenObserver<T> extends AtomicInteger implements Observ final InnerRepeatObserver inner; - final AtomicReference<Disposable> d; + final AtomicReference<Disposable> upstream; final ObservableSource<T> source; volatile boolean active; RepeatWhenObserver(Observer<? super T> actual, Subject<Object> signaller, ObservableSource<T> source) { - this.actual = actual; + this.downstream = actual; this.signaller = signaller; this.source = source; this.wip = new AtomicInteger(); this.error = new AtomicThrowable(); this.inner = new InnerRepeatObserver(); - this.d = new AtomicReference<Disposable>(); + this.upstream = new AtomicReference<Disposable>(); } @Override public void onSubscribe(Disposable d) { - DisposableHelper.replace(this.d, d); + DisposableHelper.setOnce(this.upstream, d); } @Override public void onNext(T t) { - HalfSerializer.onNext(actual, t, this, error); + HalfSerializer.onNext(downstream, t, this, error); } @Override public void onError(Throwable e) { DisposableHelper.dispose(inner); - HalfSerializer.onError(actual, e, this, error); + HalfSerializer.onError(downstream, e, this, error); } @Override public void onComplete() { + DisposableHelper.replace(upstream, null); active = false; signaller.onNext(0); } @Override public boolean isDisposed() { - return DisposableHelper.isDisposed(d.get()); + return DisposableHelper.isDisposed(upstream.get()); } @Override public void dispose() { - DisposableHelper.dispose(d); + DisposableHelper.dispose(upstream); DisposableHelper.dispose(inner); } @@ -128,13 +129,13 @@ void innerNext() { } void innerError(Throwable ex) { - DisposableHelper.dispose(d); - HalfSerializer.onError(actual, ex, this, error); + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, ex, this, error); } void innerComplete() { - DisposableHelper.dispose(d); - HalfSerializer.onComplete(actual, this, error); + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); } void subscribeNext() { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java index c48e83b74b..2818d975f1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java @@ -31,7 +31,7 @@ import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Timed; -public final class ObservableReplay<T> extends ConnectableObservable<T> implements HasUpstreamObservableSource<T>, Disposable { +public final class ObservableReplay<T> extends ConnectableObservable<T> implements HasUpstreamObservableSource<T>, ResettableConnectable { /** The source observable. */ final ObservableSource<T> source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -159,15 +159,10 @@ public ObservableSource<T> source() { return source; } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public void dispose() { - current.lazySet(null); - } - - @Override - public boolean isDisposed() { - Disposable d = current.get(); - return d == null || d.isDisposed(); + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplayObserver)connectionObject, null); } @Override @@ -371,6 +366,7 @@ public void onNext(T t) { replay(); } } + @Override public void onError(Throwable e) { // The observer front is accessed serially as required by spec so @@ -383,6 +379,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); } } + @Override public void onComplete() { // The observer front is accessed serially as required by spec so @@ -456,6 +453,8 @@ public void dispose() { cancelled = true; // remove this from the parent parent.remove(this); + // make sure the last known node is not retained + index = null; } } /** @@ -511,6 +510,7 @@ static final class UnboundedReplayBuffer<T> extends ArrayList<Object> implements UnboundedReplayBuffer(int capacityHint) { super(capacityHint); } + @Override public void next(T value) { add(NotificationLite.next(value)); @@ -619,6 +619,16 @@ final void removeFirst() { // can't null out the head's value because of late replayers would see null setFirst(next); } + + final void trimHead() { + Node head = get(); + if (head.value != null) { + Node n = new Node(null); + n.lazySet(head.get()); + set(n); + } + } + /* test */ final void removeSome(int n) { Node head = get(); while (n > 0) { @@ -628,6 +638,11 @@ final void removeFirst() { } setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } } /** * Arranges the given node is the new head from now on. @@ -678,6 +693,7 @@ public final void replay(InnerDisposable<T> output) { for (;;) { if (output.isDisposed()) { + output.index = null; return; } @@ -733,7 +749,7 @@ Object leaveTransform(Object value) { * based on its properties (i.e., truncate but the very last node). */ void truncateFinal() { - + trimHead(); } /* test */ final void collect(Collection<? super T> output) { Node n = getHead(); @@ -828,7 +844,7 @@ void truncate() { int e = 0; for (;;) { if (next != null) { - if (size > limit) { + if (size > limit && size > 1) { // never truncate the very last item just added e++; size--; prev = next; @@ -852,6 +868,7 @@ void truncate() { setFirst(prev); } } + @Override void truncateFinal() { long timeLimit = scheduler.now(unit) - maxAge; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java index 82cfe538d3..f762142f0b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java @@ -31,11 +31,11 @@ public ObservableRetryBiPredicate( } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { SequentialDisposable sa = new SequentialDisposable(); - s.onSubscribe(sa); + observer.onSubscribe(sa); - RetryBiObserver<T> rs = new RetryBiObserver<T>(s, predicate, sa, source); + RetryBiObserver<T> rs = new RetryBiObserver<T>(observer, predicate, sa, source); rs.subscribeNext(); } @@ -43,28 +43,29 @@ static final class RetryBiObserver<T> extends AtomicInteger implements Observer< private static final long serialVersionUID = -7098360935104053232L; - final Observer<? super T> actual; - final SequentialDisposable sa; + final Observer<? super T> downstream; + final SequentialDisposable upstream; final ObservableSource<? extends T> source; final BiPredicate<? super Integer, ? super Throwable> predicate; int retries; RetryBiObserver(Observer<? super T> actual, BiPredicate<? super Integer, ? super Throwable> predicate, SequentialDisposable sa, ObservableSource<? extends T> source) { - this.actual = actual; - this.sa = sa; + this.downstream = actual; + this.upstream = sa; this.source = source; this.predicate = predicate; } @Override - public void onSubscribe(Disposable s) { - sa.update(s); + public void onSubscribe(Disposable d) { + upstream.replace(d); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } + @Override public void onError(Throwable t) { boolean b; @@ -72,11 +73,11 @@ public void onError(Throwable t) { b = predicate.test(++retries, t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (!b) { - actual.onError(t); + downstream.onError(t); return; } subscribeNext(); @@ -84,7 +85,7 @@ public void onError(Throwable t) { @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } /** @@ -94,7 +95,7 @@ void subscribeNext() { if (getAndIncrement() == 0) { int missed = 1; for (;;) { - if (sa.isDisposed()) { + if (upstream.isDisposed()) { return; } source.subscribe(this); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java index 33a52d84a0..ee5e074f58 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java @@ -33,11 +33,11 @@ public ObservableRetryPredicate(Observable<T> source, } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { SequentialDisposable sa = new SequentialDisposable(); - s.onSubscribe(sa); + observer.onSubscribe(sa); - RepeatObserver<T> rs = new RepeatObserver<T>(s, count, predicate, sa, source); + RepeatObserver<T> rs = new RepeatObserver<T>(observer, count, predicate, sa, source); rs.subscribeNext(); } @@ -45,29 +45,30 @@ static final class RepeatObserver<T> extends AtomicInteger implements Observer<T private static final long serialVersionUID = -7098360935104053232L; - final Observer<? super T> actual; - final SequentialDisposable sa; + final Observer<? super T> downstream; + final SequentialDisposable upstream; final ObservableSource<? extends T> source; final Predicate<? super Throwable> predicate; long remaining; RepeatObserver(Observer<? super T> actual, long count, Predicate<? super Throwable> predicate, SequentialDisposable sa, ObservableSource<? extends T> source) { - this.actual = actual; - this.sa = sa; + this.downstream = actual; + this.upstream = sa; this.source = source; this.predicate = predicate; this.remaining = count; } @Override - public void onSubscribe(Disposable s) { - sa.update(s); + public void onSubscribe(Disposable d) { + upstream.replace(d); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } + @Override public void onError(Throwable t) { long r = remaining; @@ -75,18 +76,18 @@ public void onError(Throwable t) { remaining = r - 1; } if (r == 0) { - actual.onError(t); + downstream.onError(t); } else { boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(new CompositeException(t, e)); + downstream.onError(new CompositeException(t, e)); return; } if (!b) { - actual.onError(t); + downstream.onError(t); return; } subscribeNext(); @@ -95,7 +96,7 @@ public void onError(Throwable t) { @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } /** @@ -105,7 +106,7 @@ void subscribeNext() { if (getAndIncrement() == 0) { int missed = 1; for (;;) { - if (sa.isDisposed()) { + if (upstream.isDisposed()) { return; } source.subscribe(this); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java index 8c8a780b04..0d48ef1239 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java @@ -64,7 +64,7 @@ static final class RepeatWhenObserver<T> extends AtomicInteger implements Observ private static final long serialVersionUID = 802743776666017014L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final AtomicInteger wip; @@ -74,34 +74,35 @@ static final class RepeatWhenObserver<T> extends AtomicInteger implements Observ final InnerRepeatObserver inner; - final AtomicReference<Disposable> d; + final AtomicReference<Disposable> upstream; final ObservableSource<T> source; volatile boolean active; RepeatWhenObserver(Observer<? super T> actual, Subject<Throwable> signaller, ObservableSource<T> source) { - this.actual = actual; + this.downstream = actual; this.signaller = signaller; this.source = source; this.wip = new AtomicInteger(); this.error = new AtomicThrowable(); this.inner = new InnerRepeatObserver(); - this.d = new AtomicReference<Disposable>(); + this.upstream = new AtomicReference<Disposable>(); } @Override public void onSubscribe(Disposable d) { - DisposableHelper.replace(this.d, d); + DisposableHelper.replace(this.upstream, d); } @Override public void onNext(T t) { - HalfSerializer.onNext(actual, t, this, error); + HalfSerializer.onNext(downstream, t, this, error); } @Override public void onError(Throwable e) { + DisposableHelper.replace(upstream, null); active = false; signaller.onNext(e); } @@ -109,17 +110,17 @@ public void onError(Throwable e) { @Override public void onComplete() { DisposableHelper.dispose(inner); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } @Override public boolean isDisposed() { - return DisposableHelper.isDisposed(d.get()); + return DisposableHelper.isDisposed(upstream.get()); } @Override public void dispose() { - DisposableHelper.dispose(d); + DisposableHelper.dispose(upstream); DisposableHelper.dispose(inner); } @@ -128,13 +129,13 @@ void innerNext() { } void innerError(Throwable ex) { - DisposableHelper.dispose(d); - HalfSerializer.onError(actual, ex, this, error); + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, ex, this, error); } void innerComplete() { - DisposableHelper.dispose(d); - HalfSerializer.onComplete(actual, this, error); + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); } void subscribeNext() { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java index 9a420fa186..a553a8dbb0 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java @@ -36,7 +36,6 @@ public ObservableSampleTimed(ObservableSource<T> source, long period, TimeUnit u this.emitLast = emitLast; } - @Override public void subscribeActual(Observer<? super T> t) { SerializedObserver<T> serial = new SerializedObserver<T>(t); @@ -51,30 +50,30 @@ abstract static class SampleTimedObserver<T> extends AtomicReference<T> implemen private static final long serialVersionUID = -3517602651313910099L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final long period; final TimeUnit unit; final Scheduler scheduler; final AtomicReference<Disposable> timer = new AtomicReference<Disposable>(); - Disposable s; + Disposable upstream; SampleTimedObserver(Observer<? super T> actual, long period, TimeUnit unit, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.period = period; this.unit = unit; this.scheduler = scheduler; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); - Disposable d = scheduler.schedulePeriodicallyDirect(this, period, period, unit); - DisposableHelper.replace(timer, d); + Disposable task = scheduler.schedulePeriodicallyDirect(this, period, period, unit); + DisposableHelper.replace(timer, task); } } @@ -86,7 +85,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { cancelTimer(); - actual.onError(t); + downstream.onError(t); } @Override @@ -102,18 +101,18 @@ void cancelTimer() { @Override public void dispose() { cancelTimer(); - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } void emit() { T value = getAndSet(null); if (value != null) { - actual.onNext(value); + downstream.onNext(value); } } @@ -130,7 +129,7 @@ static final class SampleTimedNoLast<T> extends SampleTimedObserver<T> { @Override void complete() { - actual.onComplete(); + downstream.onComplete(); } @Override @@ -154,7 +153,7 @@ static final class SampleTimedEmitLast<T> extends SampleTimedObserver<T> { void complete() { emit(); if (wip.decrementAndGet() == 0) { - actual.onComplete(); + downstream.onComplete(); } } @@ -163,7 +162,7 @@ public void run() { if (wip.incrementAndGet() == 2) { emit(); if (wip.decrementAndGet() == 0) { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java index 5a6f092ce1..1d5f8a5fe1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java @@ -47,23 +47,23 @@ abstract static class SampleMainObserver<T> extends AtomicReference<T> private static final long serialVersionUID = -3517602651313910099L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final ObservableSource<?> sampler; final AtomicReference<Disposable> other = new AtomicReference<Disposable>(); - Disposable s; + Disposable upstream; SampleMainObserver(Observer<? super T> actual, ObservableSource<?> other) { - this.actual = actual; + this.downstream = actual; this.sampler = other; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); if (other.get() == null) { sampler.subscribe(new SamplerObserver<T>(this)); } @@ -78,13 +78,13 @@ public void onNext(T t) { @Override public void onError(Throwable t) { DisposableHelper.dispose(other); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { DisposableHelper.dispose(other); - completeMain(); + completion(); } boolean setOther(Disposable o) { @@ -94,7 +94,7 @@ boolean setOther(Disposable o) { @Override public void dispose() { DisposableHelper.dispose(other); - s.dispose(); + upstream.dispose(); } @Override @@ -103,25 +103,23 @@ public boolean isDisposed() { } public void error(Throwable e) { - s.dispose(); - actual.onError(e); + upstream.dispose(); + downstream.onError(e); } public void complete() { - s.dispose(); - completeOther(); + upstream.dispose(); + completion(); } void emit() { T value = getAndSet(null); if (value != null) { - actual.onNext(value); + downstream.onNext(value); } } - abstract void completeMain(); - - abstract void completeOther(); + abstract void completion(); abstract void run(); } @@ -134,8 +132,8 @@ static final class SamplerObserver<T> implements Observer<Object> { } @Override - public void onSubscribe(Disposable s) { - parent.setOther(s); + public void onSubscribe(Disposable d) { + parent.setOther(d); } @Override @@ -163,13 +161,8 @@ static final class SampleMainNoLast<T> extends SampleMainObserver<T> { } @Override - void completeMain() { - actual.onComplete(); - } - - @Override - void completeOther() { - actual.onComplete(); + void completion() { + downstream.onComplete(); } @Override @@ -192,20 +185,11 @@ static final class SampleMainEmitLast<T> extends SampleMainObserver<T> { } @Override - void completeMain() { - done = true; - if (wip.getAndIncrement() == 0) { - emit(); - actual.onComplete(); - } - } - - @Override - void completeOther() { + void completion() { done = true; if (wip.getAndIncrement() == 0) { emit(); - actual.onComplete(); + downstream.onComplete(); } } @@ -216,7 +200,7 @@ void run() { boolean d = done; emit(); if (d) { - actual.onComplete(); + downstream.onComplete(); return; } } while (wip.decrementAndGet() != 0); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java index 418e6514f9..d58437900c 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java @@ -136,12 +136,12 @@ static final class ScalarXMapObservable<T, R> extends Observable<R> { @SuppressWarnings("unchecked") @Override - public void subscribeActual(Observer<? super R> s) { + public void subscribeActual(Observer<? super R> observer) { ObservableSource<? extends R> other; try { other = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null ObservableSource"); } catch (Throwable e) { - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } if (other instanceof Callable) { @@ -151,19 +151,19 @@ public void subscribeActual(Observer<? super R> s) { u = ((Callable<R>)other).call(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptyDisposable.error(ex, s); + EmptyDisposable.error(ex, observer); return; } if (u == null) { - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); return; } - ScalarDisposable<R> sd = new ScalarDisposable<R>(s, u); - s.onSubscribe(sd); + ScalarDisposable<R> sd = new ScalarDisposable<R>(observer, u); + observer.onSubscribe(sd); sd.run(); } else { - other.subscribe(s); + other.subscribe(observer); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java index 2e3a6b4fda..6b830e8d8f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java @@ -34,46 +34,44 @@ public void subscribeActual(Observer<? super T> t) { } static final class ScanObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final BiFunction<T, T, T> accumulator; - Disposable s; + Disposable upstream; T value; boolean done; ScanObserver(Observer<? super T> actual, BiFunction<T, T, T> accumulator) { - this.actual = actual; + this.downstream = actual; this.accumulator = accumulator; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { return; } - final Observer<? super T> a = actual; + final Observer<? super T> a = downstream; T v = value; if (v == null) { value = t; @@ -85,7 +83,7 @@ public void onNext(T t) { u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The value returned by the accumulator is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } @@ -102,7 +100,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -111,7 +109,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java index 6eda15b035..a9b4a63716 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java @@ -48,41 +48,40 @@ public void subscribeActual(Observer<? super R> t) { } static final class ScanSeedObserver<T, R> implements Observer<T>, Disposable { - final Observer<? super R> actual; + final Observer<? super R> downstream; final BiFunction<R, ? super T, R> accumulator; R value; - Disposable s; + Disposable upstream; boolean done; ScanSeedObserver(Observer<? super R> actual, BiFunction<R, ? super T, R> accumulator, R value) { - this.actual = actual; + this.downstream = actual; this.accumulator = accumulator; this.value = value; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); - actual.onNext(value); + downstream.onNext(value); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override @@ -99,14 +98,14 @@ public void onNext(T t) { u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } value = u; - actual.onNext(u); + downstream.onNext(u); } @Override @@ -116,7 +115,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -125,7 +124,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java index c2bf67f605..07da31f3f0 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java @@ -37,16 +37,16 @@ public ObservableSequenceEqual(ObservableSource<? extends T> first, ObservableSo } @Override - public void subscribeActual(Observer<? super Boolean> s) { - EqualCoordinator<T> ec = new EqualCoordinator<T>(s, bufferSize, first, second, comparer); - s.onSubscribe(ec); + public void subscribeActual(Observer<? super Boolean> observer) { + EqualCoordinator<T> ec = new EqualCoordinator<T>(observer, bufferSize, first, second, comparer); + observer.onSubscribe(ec); ec.subscribe(); } static final class EqualCoordinator<T> extends AtomicInteger implements Disposable { private static final long serialVersionUID = -6178010334400373240L; - final Observer<? super Boolean> actual; + final Observer<? super Boolean> downstream; final BiPredicate<? super T, ? super T> comparer; final ArrayCompositeDisposable resources; final ObservableSource<? extends T> first; @@ -62,7 +62,7 @@ static final class EqualCoordinator<T> extends AtomicInteger implements Disposab EqualCoordinator(Observer<? super Boolean> actual, int bufferSize, ObservableSource<? extends T> first, ObservableSource<? extends T> second, BiPredicate<? super T, ? super T> comparer) { - this.actual = actual; + this.downstream = actual; this.first = first; this.second = second; this.comparer = comparer; @@ -74,8 +74,8 @@ static final class EqualCoordinator<T> extends AtomicInteger implements Disposab this.resources = new ArrayCompositeDisposable(2); } - boolean setDisposable(Disposable s, int index) { - return resources.setResource(index, s); + boolean setDisposable(Disposable d, int index) { + return resources.setResource(index, d); } void subscribe() { @@ -117,10 +117,10 @@ void drain() { int missed = 1; EqualObserver<T>[] as = observers; - final EqualObserver<T> s1 = as[0]; - final SpscLinkedArrayQueue<T> q1 = s1.queue; - final EqualObserver<T> s2 = as[1]; - final SpscLinkedArrayQueue<T> q2 = s2.queue; + final EqualObserver<T> observer1 = as[0]; + final SpscLinkedArrayQueue<T> q1 = observer1.queue; + final EqualObserver<T> observer2 = as[1]; + final SpscLinkedArrayQueue<T> q2 = observer2.queue; for (;;) { @@ -131,25 +131,25 @@ void drain() { return; } - boolean d1 = s1.done; + boolean d1 = observer1.done; if (d1) { - Throwable e = s1.error; + Throwable e = observer1.error; if (e != null) { cancel(q1, q2); - actual.onError(e); + downstream.onError(e); return; } } - boolean d2 = s2.done; + boolean d2 = observer2.done; if (d2) { - Throwable e = s2.error; + Throwable e = observer2.error; if (e != null) { cancel(q1, q2); - actual.onError(e); + downstream.onError(e); return; } } @@ -165,15 +165,15 @@ void drain() { boolean e2 = v2 == null; if (d1 && d2 && e1 && e2) { - actual.onNext(true); - actual.onComplete(); + downstream.onNext(true); + downstream.onComplete(); return; } if ((d1 && d2) && (e1 != e2)) { cancel(q1, q2); - actual.onNext(false); - actual.onComplete(); + downstream.onNext(false); + downstream.onComplete(); return; } @@ -186,15 +186,15 @@ void drain() { Exceptions.throwIfFatal(ex); cancel(q1, q2); - actual.onError(ex); + downstream.onError(ex); return; } if (!c) { cancel(q1, q2); - actual.onNext(false); - actual.onComplete(); + downstream.onNext(false); + downstream.onComplete(); return; } @@ -230,8 +230,8 @@ static final class EqualObserver<T> implements Observer<T> { } @Override - public void onSubscribe(Disposable s) { - parent.setDisposable(s, index); + public void onSubscribe(Disposable d) { + parent.setDisposable(d, index); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java index 8d227e699d..88e059a5cb 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java @@ -39,9 +39,9 @@ public ObservableSequenceEqualSingle(ObservableSource<? extends T> first, Observ } @Override - public void subscribeActual(SingleObserver<? super Boolean> s) { - EqualCoordinator<T> ec = new EqualCoordinator<T>(s, bufferSize, first, second, comparer); - s.onSubscribe(ec); + public void subscribeActual(SingleObserver<? super Boolean> observer) { + EqualCoordinator<T> ec = new EqualCoordinator<T>(observer, bufferSize, first, second, comparer); + observer.onSubscribe(ec); ec.subscribe(); } @@ -53,7 +53,7 @@ public Observable<Boolean> fuseToObservable() { static final class EqualCoordinator<T> extends AtomicInteger implements Disposable { private static final long serialVersionUID = -6178010334400373240L; - final SingleObserver<? super Boolean> actual; + final SingleObserver<? super Boolean> downstream; final BiPredicate<? super T, ? super T> comparer; final ArrayCompositeDisposable resources; final ObservableSource<? extends T> first; @@ -69,7 +69,7 @@ static final class EqualCoordinator<T> extends AtomicInteger implements Disposab EqualCoordinator(SingleObserver<? super Boolean> actual, int bufferSize, ObservableSource<? extends T> first, ObservableSource<? extends T> second, BiPredicate<? super T, ? super T> comparer) { - this.actual = actual; + this.downstream = actual; this.first = first; this.second = second; this.comparer = comparer; @@ -81,8 +81,8 @@ static final class EqualCoordinator<T> extends AtomicInteger implements Disposab this.resources = new ArrayCompositeDisposable(2); } - boolean setDisposable(Disposable s, int index) { - return resources.setResource(index, s); + boolean setDisposable(Disposable d, int index) { + return resources.setResource(index, d); } void subscribe() { @@ -124,10 +124,10 @@ void drain() { int missed = 1; EqualObserver<T>[] as = observers; - final EqualObserver<T> s1 = as[0]; - final SpscLinkedArrayQueue<T> q1 = s1.queue; - final EqualObserver<T> s2 = as[1]; - final SpscLinkedArrayQueue<T> q2 = s2.queue; + final EqualObserver<T> observer1 = as[0]; + final SpscLinkedArrayQueue<T> q1 = observer1.queue; + final EqualObserver<T> observer2 = as[1]; + final SpscLinkedArrayQueue<T> q2 = observer2.queue; for (;;) { @@ -138,25 +138,25 @@ void drain() { return; } - boolean d1 = s1.done; + boolean d1 = observer1.done; if (d1) { - Throwable e = s1.error; + Throwable e = observer1.error; if (e != null) { cancel(q1, q2); - actual.onError(e); + downstream.onError(e); return; } } - boolean d2 = s2.done; + boolean d2 = observer2.done; if (d2) { - Throwable e = s2.error; + Throwable e = observer2.error; if (e != null) { cancel(q1, q2); - actual.onError(e); + downstream.onError(e); return; } } @@ -172,13 +172,13 @@ void drain() { boolean e2 = v2 == null; if (d1 && d2 && e1 && e2) { - actual.onSuccess(true); + downstream.onSuccess(true); return; } if ((d1 && d2) && (e1 != e2)) { cancel(q1, q2); - actual.onSuccess(false); + downstream.onSuccess(false); return; } @@ -191,14 +191,14 @@ void drain() { Exceptions.throwIfFatal(ex); cancel(q1, q2); - actual.onError(ex); + downstream.onError(ex); return; } if (!c) { cancel(q1, q2); - actual.onSuccess(false); + downstream.onSuccess(false); return; } @@ -234,8 +234,8 @@ static final class EqualObserver<T> implements Observer<T> { } @Override - public void onSubscribe(Disposable s) { - parent.setDisposable(s, index); + public void onSubscribe(Disposable d) { + parent.setDisposable(d, index); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java index 3dc75441bf..d4e1b9a375 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java @@ -25,44 +25,43 @@ public final class ObservableSingleMaybe<T> extends Maybe<T> { public ObservableSingleMaybe(ObservableSource<T> source) { this.source = source; } + @Override public void subscribeActual(MaybeObserver<? super T> t) { source.subscribe(new SingleElementObserver<T>(t)); } static final class SingleElementObserver<T> implements Observer<T>, Disposable { - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; - Disposable s; + Disposable upstream; T value; boolean done; - SingleElementObserver(MaybeObserver<? super T> actual) { - this.actual = actual; + SingleElementObserver(MaybeObserver<? super T> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -70,8 +69,8 @@ public void onNext(T t) { } if (value != null) { done = true; - s.dispose(); - actual.onError(new IllegalArgumentException("Sequence contains more than one element!")); + upstream.dispose(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); return; } value = t; @@ -84,7 +83,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -96,9 +95,9 @@ public void onComplete() { T v = value; value = null; if (v == null) { - actual.onComplete(); + downstream.onComplete(); } else { - actual.onSuccess(v); + downstream.onSuccess(v); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java index 40ac65da57..e1232f849f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java @@ -36,41 +36,39 @@ public void subscribeActual(SingleObserver<? super T> t) { } static final class SingleElementObserver<T> implements Observer<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final T defaultValue; - Disposable s; + Disposable upstream; T value; boolean done; SingleElementObserver(SingleObserver<? super T> actual, T defaultValue) { - this.actual = actual; + this.downstream = actual; this.defaultValue = defaultValue; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -78,8 +76,8 @@ public void onNext(T t) { } if (value != null) { done = true; - s.dispose(); - actual.onError(new IllegalArgumentException("Sequence contains more than one element!")); + upstream.dispose(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); return; } value = t; @@ -92,7 +90,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -108,9 +106,9 @@ public void onComplete() { } if (v != null) { - actual.onSuccess(v); + downstream.onSuccess(v); } else { - actual.onError(new NoSuchElementException()); + downstream.onError(new NoSuchElementException()); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java index 5eeefde388..a03bca541f 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java @@ -15,6 +15,7 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; public final class ObservableSkip<T> extends AbstractObservableWithUpstream<T, T> { final long n; @@ -24,25 +25,27 @@ public ObservableSkip(ObservableSource<T> source, long n) { } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new SkipObserver<T>(s, n)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new SkipObserver<T>(observer, n)); } static final class SkipObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; long remaining; - Disposable d; + Disposable upstream; SkipObserver(Observer<? super T> actual, long n) { - this.actual = actual; + this.downstream = actual; this.remaining = n; } @Override - public void onSubscribe(Disposable s) { - this.d = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } } @Override @@ -50,28 +53,28 @@ public void onNext(T t) { if (remaining != 0L) { remaining--; } else { - actual.onNext(t); + downstream.onNext(t); } } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java index be257cd9c8..2ea5953c39 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java @@ -28,59 +28,58 @@ public ObservableSkipLast(ObservableSource<T> source, int skip) { } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new SkipLastObserver<T>(s, skip)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new SkipLastObserver<T>(observer, skip)); } static final class SkipLastObserver<T> extends ArrayDeque<T> implements Observer<T>, Disposable { private static final long serialVersionUID = -3807491841935125653L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final int skip; - Disposable s; + Disposable upstream; SkipLastObserver(Observer<? super T> actual, int skip) { super(skip); - this.actual = actual; + this.downstream = actual; this.skip = skip; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override public void onNext(T t) { if (skip == size()) { - actual.onNext(poll()); + downstream.onNext(poll()); } offer(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java index b24d341245..3c9eb40261 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java @@ -46,14 +46,14 @@ public void subscribeActual(Observer<? super T> t) { static final class SkipLastTimedObserver<T> extends AtomicInteger implements Observer<T>, Disposable { private static final long serialVersionUID = -5677354903406201275L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final long time; final TimeUnit unit; final Scheduler scheduler; final SpscLinkedArrayQueue<Object> queue; final boolean delayError; - Disposable s; + Disposable upstream; volatile boolean cancelled; @@ -61,7 +61,7 @@ static final class SkipLastTimedObserver<T> extends AtomicInteger implements Obs Throwable error; SkipLastTimedObserver(Observer<? super T> actual, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.time = time; this.unit = unit; this.scheduler = scheduler; @@ -70,10 +70,10 @@ static final class SkipLastTimedObserver<T> extends AtomicInteger implements Obs } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -105,7 +105,7 @@ public void onComplete() { public void dispose() { if (!cancelled) { cancelled = true; - s.dispose(); + upstream.dispose(); if (getAndIncrement() == 0) { queue.clear(); @@ -125,7 +125,7 @@ void drain() { int missed = 1; - final Observer<? super T> a = actual; + final Observer<? super T> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; final boolean delayError = this.delayError; final TimeUnit unit = this.unit; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java index c9f711ff3e..f02edecf0e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java @@ -43,56 +43,56 @@ public void subscribeActual(Observer<? super T> child) { static final class SkipUntilObserver<T> implements Observer<T> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final ArrayCompositeDisposable frc; - Disposable s; + Disposable upstream; volatile boolean notSkipping; boolean notSkippingLocal; SkipUntilObserver(Observer<? super T> actual, ArrayCompositeDisposable frc) { - this.actual = actual; + this.downstream = actual; this.frc = frc; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - frc.setResource(0, s); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + frc.setResource(0, d); } } @Override public void onNext(T t) { if (notSkippingLocal) { - actual.onNext(t); + downstream.onNext(t); } else if (notSkipping) { notSkippingLocal = true; - actual.onNext(t); + downstream.onNext(t); } } @Override public void onError(Throwable t) { frc.dispose(); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { frc.dispose(); - actual.onComplete(); + downstream.onComplete(); } } final class SkipUntil implements Observer<U> { - private final ArrayCompositeDisposable frc; - private final SkipUntilObserver<T> sus; - private final SerializedObserver<T> serial; - Disposable s; + final ArrayCompositeDisposable frc; + final SkipUntilObserver<T> sus; + final SerializedObserver<T> serial; + Disposable upstream; SkipUntil(ArrayCompositeDisposable frc, SkipUntilObserver<T> sus, SerializedObserver<T> serial) { this.frc = frc; @@ -101,16 +101,16 @@ final class SkipUntil implements Observer<U> { } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - frc.setResource(1, s); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + frc.setResource(1, d); } } @Override public void onNext(U t) { - s.dispose(); + upstream.dispose(); sus.notSkipping = true; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java index a46e27b22e..d452fdaf0a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java @@ -27,69 +27,67 @@ public ObservableSkipWhile(ObservableSource<T> source, Predicate<? super T> pred } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new SkipWhileObserver<T>(s, predicate)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new SkipWhileObserver<T>(observer, predicate)); } static final class SkipWhileObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean notSkipping; SkipWhileObserver(Observer<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (notSkipping) { - actual.onNext(t); + downstream.onNext(t); } else { boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); - actual.onError(e); + upstream.dispose(); + downstream.onError(e); return; } if (!b) { notSkipping = true; - actual.onNext(t); + downstream.onNext(t); } } } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java index 57f4666b52..7e697d379a 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java @@ -28,10 +28,10 @@ public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) { } @Override - public void subscribeActual(final Observer<? super T> s) { - final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s); + public void subscribeActual(final Observer<? super T> observer) { + final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer); - s.onSubscribe(parent); + observer.onSubscribe(parent); parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent))); } @@ -39,38 +39,38 @@ public void subscribeActual(final Observer<? super T> s) { static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable { private static final long serialVersionUID = 8094547886072529208L; - final Observer<? super T> actual; + final Observer<? super T> downstream; - final AtomicReference<Disposable> s; + final AtomicReference<Disposable> upstream; - SubscribeOnObserver(Observer<? super T> actual) { - this.actual = actual; - this.s = new AtomicReference<Disposable>(); + SubscribeOnObserver(Observer<? super T> downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference<Disposable>(); } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); DisposableHelper.dispose(this); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java index aa97b7f18a..6eec37f7a1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java @@ -32,22 +32,22 @@ public void subscribeActual(Observer<? super T> t) { } static final class SwitchIfEmptyObserver<T> implements Observer<T> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final ObservableSource<? extends T> other; final SequentialDisposable arbiter; boolean empty; SwitchIfEmptyObserver(Observer<? super T> actual, ObservableSource<? extends T> other) { - this.actual = actual; + this.downstream = actual; this.other = other; this.empty = true; this.arbiter = new SequentialDisposable(); } @Override - public void onSubscribe(Disposable s) { - arbiter.update(s); + public void onSubscribe(Disposable d) { + arbiter.update(d); } @Override @@ -55,12 +55,12 @@ public void onNext(T t) { if (empty) { empty = false; } - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override @@ -69,7 +69,7 @@ public void onComplete() { empty = false; other.subscribe(this); } else { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java index bf7fbaf225..4e97ea405d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java @@ -21,7 +21,8 @@ import io.reactivex.functions.Function; import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.queue.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; import io.reactivex.internal.util.AtomicThrowable; import io.reactivex.plugins.RxJavaPlugins; @@ -53,7 +54,7 @@ public void subscribeActual(Observer<? super R> t) { static final class SwitchMapObserver<T, R> extends AtomicInteger implements Observer<T>, Disposable { private static final long serialVersionUID = -3491074160481096299L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends ObservableSource<? extends R>> mapper; final int bufferSize; @@ -65,7 +66,7 @@ static final class SwitchMapObserver<T, R> extends AtomicInteger implements Obse volatile boolean cancelled; - Disposable s; + Disposable upstream; final AtomicReference<SwitchMapInnerObserver<T, R>> active = new AtomicReference<SwitchMapInnerObserver<T, R>>(); @@ -80,7 +81,7 @@ static final class SwitchMapObserver<T, R> extends AtomicInteger implements Obse SwitchMapObserver(Observer<? super R> actual, Function<? super T, ? extends ObservableSource<? extends R>> mapper, int bufferSize, boolean delayErrors) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.bufferSize = bufferSize; this.delayErrors = delayErrors; @@ -88,10 +89,10 @@ static final class SwitchMapObserver<T, R> extends AtomicInteger implements Obse } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -110,7 +111,7 @@ public void onNext(T t) { p = ObjectHelper.requireNonNull(mapper.apply(t), "The ObservableSource returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } @@ -131,15 +132,15 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (done || !errors.addThrowable(t)) { + if (!done && errors.addThrowable(t)) { if (!delayErrors) { disposeInner(); } + done = true; + drain(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - drain(); } @Override @@ -154,7 +155,7 @@ public void onComplete() { public void dispose() { if (!cancelled) { cancelled = true; - s.dispose(); + upstream.dispose(); disposeInner(); } } @@ -180,7 +181,9 @@ void drain() { return; } - final Observer<? super R> a = actual; + final Observer<? super R> a = downstream; + final AtomicReference<SwitchMapInnerObserver<T, R>> active = this.active; + final boolean delayErrors = this.delayErrors; int missing = 1; @@ -218,66 +221,85 @@ void drain() { SwitchMapInnerObserver<T, R> inner = active.get(); if (inner != null) { - SpscLinkedArrayQueue<R> q = inner.queue; - - if (inner.done) { - boolean empty = q.isEmpty(); - if (delayErrors) { - if (empty) { - active.compareAndSet(inner, null); - continue; + SimpleQueue<R> q = inner.queue; + + if (q != null) { + if (inner.done) { + boolean empty = q.isEmpty(); + if (delayErrors) { + if (empty) { + active.compareAndSet(inner, null); + continue; + } + } else { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + return; + } + if (empty) { + active.compareAndSet(inner, null); + continue; + } } - } else { - Throwable ex = errors.get(); - if (ex != null) { - a.onError(errors.terminate()); + } + + boolean retry = false; + + for (;;) { + if (cancelled) { return; } - if (empty) { - active.compareAndSet(inner, null); - continue; + if (inner != active.get()) { + retry = true; + break; } - } - } - boolean retry = false; + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + return; + } + } - for (;;) { - if (cancelled) { - return; - } - if (inner != active.get()) { - retry = true; - break; - } + boolean d = inner.done; + R v; - if (!delayErrors) { - Throwable ex = errors.get(); - if (ex != null) { - a.onError(errors.terminate()); - return; + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + active.compareAndSet(inner, null); + if (!delayErrors) { + disposeInner(); + upstream.dispose(); + done = true; + } else { + inner.cancel(); + } + v = null; + retry = true; } - } + boolean empty = v == null; - boolean d = inner.done; - R v = q.poll(); - boolean empty = v == null; + if (d && empty) { + active.compareAndSet(inner, null); + retry = true; + break; + } - if (d && empty) { - active.compareAndSet(inner, null); - retry = true; - break; - } + if (empty) { + break; + } - if (empty) { - break; + a.onNext(v); } - a.onNext(v); - } - - if (retry) { - continue; + if (retry) { + continue; + } } } @@ -291,7 +313,8 @@ void drain() { void innerError(SwitchMapInnerObserver<T, R> inner, Throwable ex) { if (inner.index == unique && errors.addThrowable(ex)) { if (!delayErrors) { - s.dispose(); + upstream.dispose(); + done = true; } inner.done = true; drain(); @@ -306,25 +329,49 @@ static final class SwitchMapInnerObserver<T, R> extends AtomicReference<Disposab private static final long serialVersionUID = 3837284832786408377L; final SwitchMapObserver<T, R> parent; final long index; - final SpscLinkedArrayQueue<R> queue; + + final int bufferSize; + + volatile SimpleQueue<R> queue; volatile boolean done; SwitchMapInnerObserver(SwitchMapObserver<T, R> parent, long index, int bufferSize) { this.parent = parent; this.index = index; - this.queue = new SpscLinkedArrayQueue<R>(bufferSize); + this.bufferSize = bufferSize; } @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this, s); + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable<R> qd = (QueueDisposable<R>) d; + + int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + if (m == QueueDisposable.SYNC) { + queue = qd; + done = true; + parent.drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + queue = qd; + return; + } + } + + queue = new SpscLinkedArrayQueue<R>(bufferSize); + } } @Override public void onNext(R t) { if (index == parent.unique) { - queue.offer(t); + if (t != null) { + queue.offer(t); + } parent.drain(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java index 418ec23e7a..2796357c80 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java @@ -31,40 +31,43 @@ protected void subscribeActual(Observer<? super T> observer) { } static final class TakeObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; boolean done; - Disposable subscription; + Disposable upstream; long remaining; TakeObserver(Observer<? super T> actual, long limit) { - this.actual = actual; + this.downstream = actual; this.remaining = limit; } + @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.subscription, s)) { - subscription = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + upstream = d; if (remaining == 0) { done = true; - s.dispose(); - EmptyDisposable.complete(actual); + d.dispose(); + EmptyDisposable.complete(downstream); } else { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } } + @Override public void onNext(T t) { if (!done && remaining-- > 0) { boolean stop = remaining == 0; - actual.onNext(t); + downstream.onNext(t); if (stop) { onComplete(); } } } + @Override public void onError(Throwable t) { if (done) { @@ -73,26 +76,27 @@ public void onError(Throwable t) { } done = true; - subscription.dispose(); - actual.onError(t); + upstream.dispose(); + downstream.onError(t); } + @Override public void onComplete() { if (!done) { done = true; - subscription.dispose(); - actual.onComplete(); + upstream.dispose(); + downstream.onComplete(); } } @Override public void dispose() { - subscription.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return subscription.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java index 239ec3dea2..92f0510f11 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java @@ -35,23 +35,23 @@ public void subscribeActual(Observer<? super T> t) { static final class TakeLastObserver<T> extends ArrayDeque<T> implements Observer<T>, Disposable { private static final long serialVersionUID = 7240042530241604978L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final int count; - Disposable s; + Disposable upstream; volatile boolean cancelled; TakeLastObserver(Observer<? super T> actual, int count) { - this.actual = actual; + this.downstream = actual; this.count = count; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -65,12 +65,12 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - Observer<? super T> a = actual; + Observer<? super T> a = downstream; for (;;) { if (cancelled) { return; @@ -90,7 +90,7 @@ public void onComplete() { public void dispose() { if (!cancelled) { cancelled = true; - s.dispose(); + upstream.dispose(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java index e0c765ec98..353f51fcf9 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java @@ -23,26 +23,26 @@ public ObservableTakeLastOne(ObservableSource<T> source) { } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new TakeLastOneObserver<T>(s)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new TakeLastOneObserver<T>(observer)); } static final class TakeLastOneObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; - Disposable s; + Disposable upstream; T value; - TakeLastOneObserver(Observer<? super T> actual) { - this.actual = actual; + TakeLastOneObserver(Observer<? super T> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -54,7 +54,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { value = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -66,20 +66,20 @@ void emit() { T v = value; if (v != null) { value = null; - actual.onNext(v); + downstream.onNext(v); } - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { value = null; - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java index d72d4c2171..213588032b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java @@ -49,7 +49,7 @@ static final class TakeLastTimedObserver<T> extends AtomicBoolean implements Observer<T>, Disposable { private static final long serialVersionUID = -5677354903406201275L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final long count; final long time; final TimeUnit unit; @@ -57,14 +57,14 @@ static final class TakeLastTimedObserver<T> final SpscLinkedArrayQueue<Object> queue; final boolean delayError; - Disposable d; + Disposable upstream; volatile boolean cancelled; Throwable error; TakeLastTimedObserver(Observer<? super T> actual, long count, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.count = count; this.time = time; this.unit = unit; @@ -75,9 +75,9 @@ static final class TakeLastTimedObserver<T> @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -118,7 +118,7 @@ public void onComplete() { public void dispose() { if (!cancelled) { cancelled = true; - d.dispose(); + upstream.dispose(); if (compareAndSet(false, true)) { queue.clear(); @@ -136,9 +136,10 @@ void drain() { return; } - final Observer<? super T> a = actual; + final Observer<? super T> a = downstream; final SpscLinkedArrayQueue<Object> q = queue; final boolean delayError = this.delayError; + final long timestampLimit = scheduler.now(unit) - time; for (;;) { if (cancelled) { @@ -171,7 +172,7 @@ void drain() { @SuppressWarnings("unchecked") T o = (T)q.poll(); - if ((Long)ts < scheduler.now(unit) - time) { + if ((Long)ts < timestampLimit) { continue; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java index 64639abc0a..3b831b21d4 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java @@ -13,103 +13,121 @@ package io.reactivex.internal.operators.observable; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import io.reactivex.*; import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.*; -import io.reactivex.observers.SerializedObserver; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.*; public final class ObservableTakeUntil<T, U> extends AbstractObservableWithUpstream<T, T> { + final ObservableSource<? extends U> other; + public ObservableTakeUntil(ObservableSource<T> source, ObservableSource<? extends U> other) { super(source); this.other = other; } + @Override public void subscribeActual(Observer<? super T> child) { - final SerializedObserver<T> serial = new SerializedObserver<T>(child); - - final ArrayCompositeDisposable frc = new ArrayCompositeDisposable(2); + TakeUntilMainObserver<T, U> parent = new TakeUntilMainObserver<T, U>(child); + child.onSubscribe(parent); - final TakeUntilObserver<T> tus = new TakeUntilObserver<T>(serial, frc); + other.subscribe(parent.otherObserver); + source.subscribe(parent); + } - child.onSubscribe(frc); + static final class TakeUntilMainObserver<T, U> extends AtomicInteger + implements Observer<T>, Disposable { - other.subscribe(new TakeUntil(frc, serial)); + private static final long serialVersionUID = 1418547743690811973L; - source.subscribe(tus); - } + final Observer<? super T> downstream; - static final class TakeUntilObserver<T> extends AtomicBoolean implements Observer<T> { + final AtomicReference<Disposable> upstream; - private static final long serialVersionUID = 3451719290311127173L; - final Observer<? super T> actual; - final ArrayCompositeDisposable frc; + final OtherObserver otherObserver; - Disposable s; + final AtomicThrowable error; - TakeUntilObserver(Observer<? super T> actual, ArrayCompositeDisposable frc) { - this.actual = actual; - this.frc = frc; + TakeUntilMainObserver(Observer<? super T> downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference<Disposable>(); + this.otherObserver = new OtherObserver(); + this.error = new AtomicThrowable(); } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - frc.setResource(0, s); - } + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(otherObserver); } @Override - public void onNext(T t) { - actual.onNext(t); + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); } @Override - public void onError(Throwable t) { - frc.dispose(); - actual.onError(t); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); } @Override - public void onComplete() { - frc.dispose(); - actual.onComplete(); + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); } - } - - final class TakeUntil implements Observer<U> { - private final ArrayCompositeDisposable frc; - private final SerializedObserver<T> serial; - TakeUntil(ArrayCompositeDisposable frc, SerializedObserver<T> serial) { - this.frc = frc; - this.serial = serial; + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, e, this, error); } @Override - public void onSubscribe(Disposable s) { - frc.setResource(1, s); + public void onComplete() { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onComplete(downstream, this, error); } - @Override - public void onNext(U t) { - frc.dispose(); - serial.onComplete(); + void otherError(Throwable e) { + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, e, this, error); } - @Override - public void onError(Throwable t) { - frc.dispose(); - serial.onError(t); + void otherComplete() { + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); } - @Override - public void onComplete() { - frc.dispose(); - serial.onComplete(); + final class OtherObserver extends AtomicReference<Disposable> + implements Observer<U> { + + private static final long serialVersionUID = -8693423678067375039L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(U t) { + DisposableHelper.dispose(this); + otherComplete(); + } + + @Override + public void onError(Throwable e) { + otherError(e); + } + + @Override + public void onComplete() { + otherComplete(); + } + } } + } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java index 8af48da7e0..cd984f2e07 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java @@ -28,55 +28,55 @@ public ObservableTakeUntilPredicate(ObservableSource<T> source, Predicate<? supe } @Override - public void subscribeActual(Observer<? super T> s) { - source.subscribe(new TakeUntilPredicateObserver<T>(s, predicate)); + public void subscribeActual(Observer<? super T> observer) { + source.subscribe(new TakeUntilPredicateObserver<T>(observer, predicate)); } static final class TakeUntilPredicateObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; - TakeUntilPredicateObserver(Observer<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + TakeUntilPredicateObserver(Observer<? super T> downstream, Predicate<? super T> predicate) { + this.downstream = downstream; this.predicate = predicate; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override public void onNext(T t) { if (!done) { - actual.onNext(t); + downstream.onNext(t); boolean b; try { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (b) { done = true; - s.dispose(); - actual.onComplete(); + upstream.dispose(); + downstream.onComplete(); } } } @@ -85,7 +85,7 @@ public void onNext(T t) { public void onError(Throwable t) { if (!done) { done = true; - actual.onError(t); + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -95,7 +95,7 @@ public void onError(Throwable t) { public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java index b91898a652..c57e6dfd12 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java @@ -33,38 +33,36 @@ public void subscribeActual(Observer<? super T> t) { } static final class TakeWhileObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final Predicate<? super T> predicate; - Disposable s; + Disposable upstream; boolean done; TakeWhileObserver(Observer<? super T> actual, Predicate<? super T> predicate) { - this.actual = actual; + this.downstream = actual; this.predicate = predicate; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -75,19 +73,19 @@ public void onNext(T t) { b = predicate.test(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); - s.dispose(); + upstream.dispose(); onError(e); return; } if (!b) { done = true; - s.dispose(); - actual.onComplete(); + upstream.dispose(); + downstream.onComplete(); return; } - actual.onNext(t); + downstream.onNext(t); } @Override @@ -97,7 +95,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -106,7 +104,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java index f2512180da..ab1ce459f4 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java @@ -48,29 +48,29 @@ static final class DebounceTimedObserver<T> implements Observer<T>, Disposable, Runnable { private static final long serialVersionUID = 786994795061867455L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final long timeout; final TimeUnit unit; final Scheduler.Worker worker; - Disposable s; + Disposable upstream; volatile boolean gate; boolean done; DebounceTimedObserver(Observer<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @@ -79,7 +79,7 @@ public void onNext(T t) { if (!gate && !done) { gate = true; - actual.onNext(t); + downstream.onNext(t); Disposable d = get(); if (d != null) { @@ -87,8 +87,6 @@ public void onNext(T t) { } DisposableHelper.replace(this, worker.schedule(this, timeout, unit)); } - - } @Override @@ -102,7 +100,7 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); } else { done = true; - actual.onError(t); + downstream.onError(t); worker.dispose(); } } @@ -111,14 +109,14 @@ public void onError(Throwable t) { public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); worker.dispose(); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); worker.dispose(); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java new file mode 100644 index 0000000000..39e0f0bfc3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java @@ -0,0 +1,214 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Emits the next or latest item when the given time elapses. + * <p> + * The operator emits the next item, then starts a timer. When the timer fires, + * it tries to emit the latest item from upstream. If there was no upstream item, + * in the meantime, the next upstream item is emitted immediately and the + * timed process repeats. + * <p>History: 2.1.14 - experimental + * @param <T> the upstream and downstream value type + * @since 2.2 + */ +public final class ObservableThrottleLatest<T> extends AbstractObservableWithUpstream<T, T> { + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean emitLast; + + public ObservableThrottleLatest(Observable<T> source, + long timeout, TimeUnit unit, Scheduler scheduler, + boolean emitLast) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new ThrottleLatestObserver<T>(observer, timeout, unit, scheduler.createWorker(), emitLast)); + } + + static final class ThrottleLatestObserver<T> + extends AtomicInteger + implements Observer<T>, Disposable, Runnable { + + private static final long serialVersionUID = -8296689127439125014L; + + final Observer<? super T> downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final boolean emitLast; + + final AtomicReference<T> latest; + + Disposable upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + volatile boolean timerFired; + + boolean timerRunning; + + ThrottleLatestObserver(Observer<? super T> downstream, + long timeout, TimeUnit unit, Scheduler.Worker worker, + boolean emitLast) { + this.downstream = downstream; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.emitLast = emitLast; + this.latest = new AtomicReference<T>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + latest.set(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + worker.dispose(); + if (getAndIncrement() == 0) { + latest.lazySet(null); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + timerFired = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + AtomicReference<T> latest = this.latest; + Observer<? super T> downstream = this.downstream; + + for (;;) { + + for (;;) { + if (cancelled) { + latest.lazySet(null); + return; + } + + boolean d = done; + + if (d && error != null) { + latest.lazySet(null); + downstream.onError(error); + worker.dispose(); + return; + } + + T v = latest.get(); + boolean empty = v == null; + + if (d) { + v = latest.getAndSet(null); + if (!empty && emitLast) { + downstream.onNext(v); + } + downstream.onComplete(); + worker.dispose(); + return; + } + + if (empty) { + if (timerFired) { + timerRunning = false; + timerFired = false; + } + break; + } + + if (!timerRunning || timerFired) { + v = latest.getAndSet(null); + downstream.onNext(v); + + timerFired = false; + timerRunning = true; + worker.schedule(this, timeout, unit); + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java index a9449ecd89..7d06b18f5e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java @@ -36,57 +36,56 @@ public void subscribeActual(Observer<? super Timed<T>> t) { } static final class TimeIntervalObserver<T> implements Observer<T>, Disposable { - final Observer<? super Timed<T>> actual; + final Observer<? super Timed<T>> downstream; final TimeUnit unit; final Scheduler scheduler; long lastTime; - Disposable s; + Disposable upstream; TimeIntervalObserver(Observer<? super Timed<T>> actual, TimeUnit unit, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; this.unit = unit; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; lastTime = scheduler.now(unit); - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { long now = scheduler.now(unit); long last = lastTime; lastTime = now; long delta = now - last; - actual.onNext(new Timed<T>(t, delta, unit)); + downstream.onNext(new Timed<T>(t, delta, unit)); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java index e3b76736e6..2aeb9051a2 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java @@ -13,17 +13,16 @@ package io.reactivex.internal.operators.observable; -import io.reactivex.internal.functions.ObjectHelper; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; import io.reactivex.internal.disposables.*; -import io.reactivex.internal.observers.FullArbiterObserver; -import io.reactivex.observers.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.observable.ObservableTimeoutTimed.TimeoutSupport; import io.reactivex.plugins.RxJavaPlugins; public final class ObservableTimeout<T, U, V> extends AbstractObservableWithUpstream<T, T> { @@ -32,10 +31,10 @@ public final class ObservableTimeout<T, U, V> extends AbstractObservableWithUpst final ObservableSource<? extends T> other; public ObservableTimeout( - ObservableSource<T> source, + Observable<T> source, ObservableSource<U> firstTimeoutIndicator, Function<? super T, ? extends ObservableSource<V>> itemTimeoutIndicator, - ObservableSource<? extends T> other) { + ObservableSource<? extends T> other) { super(source); this.firstTimeoutIndicator = firstTimeoutIndicator; this.itemTimeoutIndicator = itemTimeoutIndicator; @@ -43,306 +42,337 @@ public ObservableTimeout( } @Override - public void subscribeActual(Observer<? super T> t) { + protected void subscribeActual(Observer<? super T> observer) { if (other == null) { - source.subscribe(new TimeoutObserver<T, U, V>( - new SerializedObserver<T>(t), - firstTimeoutIndicator, itemTimeoutIndicator)); + TimeoutObserver<T> parent = new TimeoutObserver<T>(observer, itemTimeoutIndicator); + observer.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); } else { - source.subscribe(new TimeoutOtherObserver<T, U, V>( - t, firstTimeoutIndicator, itemTimeoutIndicator, other)); + TimeoutFallbackObserver<T> parent = new TimeoutFallbackObserver<T>(observer, itemTimeoutIndicator, other); + observer.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); } } - static final class TimeoutObserver<T, U, V> - extends AtomicReference<Disposable> - implements Observer<T>, Disposable, OnTimeout { - - private static final long serialVersionUID = 2672739326310051084L; - final Observer<? super T> actual; - final ObservableSource<U> firstTimeoutIndicator; - final Function<? super T, ? extends ObservableSource<V>> itemTimeoutIndicator; + interface TimeoutSelectorSupport extends TimeoutSupport { + void onTimeoutError(long idx, Throwable ex); + } - Disposable s; + static final class TimeoutObserver<T> extends AtomicLong + implements Observer<T>, Disposable, TimeoutSelectorSupport { - volatile long index; + private static final long serialVersionUID = 3764492702657003550L; - TimeoutObserver(Observer<? super T> actual, - ObservableSource<U> firstTimeoutIndicator, - Function<? super T, ? extends ObservableSource<V>> itemTimeoutIndicator) { - this.actual = actual; - this.firstTimeoutIndicator = firstTimeoutIndicator; - this.itemTimeoutIndicator = itemTimeoutIndicator; - } + final Observer<? super T> downstream; - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + final Function<? super T, ? extends ObservableSource<?>> itemTimeoutIndicator; - Observer<? super T> a = actual; + final SequentialDisposable task; - ObservableSource<U> p = firstTimeoutIndicator; + final AtomicReference<Disposable> upstream; - if (p != null) { - TimeoutInnerObserver<T, U, V> tis = new TimeoutInnerObserver<T, U, V>(this, 0); + TimeoutObserver(Observer<? super T> actual, Function<? super T, ? extends ObservableSource<?>> itemTimeoutIndicator) { + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Disposable>(); + } - if (compareAndSet(null, tis)) { - a.onSubscribe(this); - p.subscribe(tis); - } - } else { - a.onSubscribe(this); - } - } + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); } @Override public void onNext(T t) { - long idx = index + 1; - index = idx; - - actual.onNext(t); + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } - Disposable d = get(); + Disposable d = task.get(); if (d != null) { d.dispose(); } - ObservableSource<V> p; + downstream.onNext(t); + + ObservableSource<?> itemTimeoutObservableSource; try { - p = ObjectHelper.requireNonNull(itemTimeoutIndicator.apply(t), "The ObservableSource returned is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - dispose(); - actual.onError(e); + itemTimeoutObservableSource = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null ObservableSource."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().dispose(); + getAndSet(Long.MAX_VALUE); + downstream.onError(ex); return; } - TimeoutInnerObserver<T, U, V> tis = new TimeoutInnerObserver<T, U, V>(this, idx); + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutObservableSource.subscribe(consumer); + } + } - if (compareAndSet(d, tis)) { - p.subscribe(tis); + void startFirstTimeout(ObservableSource<?> firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } } } @Override public void onError(Throwable t) { - DisposableHelper.dispose(this); - actual.onError(t); + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } } @Override public void onComplete() { - DisposableHelper.dispose(this); - actual.onComplete(); - } + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); - @Override - public void dispose() { - if (DisposableHelper.dispose(this)) { - s.dispose(); + downstream.onComplete(); } } @Override - public boolean isDisposed() { - return s.isDisposed(); - } + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); - @Override - public void timeout(long idx) { - if (idx == index) { - dispose(); - actual.onError(new TimeoutException()); + downstream.onError(new TimeoutException()); } } @Override - public void innerError(Throwable e) { - s.dispose(); - actual.onError(e); - } - } - - interface OnTimeout { - void timeout(long index); - - void innerError(Throwable e); - } - - static final class TimeoutInnerObserver<T, U, V> extends DisposableObserver<Object> { - final OnTimeout parent; - final long index; - - boolean done; + public void onTimeoutError(long idx, Throwable ex) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); - TimeoutInnerObserver(OnTimeout parent, final long index) { - this.parent = parent; - this.index = index; - } - - @Override - public void onNext(Object t) { - if (done) { - return; + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); } - done = true; - dispose(); - parent.timeout(index); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - done = true; - parent.innerError(t); + public void dispose() { + DisposableHelper.dispose(upstream); + task.dispose(); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - parent.timeout(index); + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); } } - static final class TimeoutOtherObserver<T, U, V> + static final class TimeoutFallbackObserver<T> extends AtomicReference<Disposable> - implements Observer<T>, Disposable, OnTimeout { + implements Observer<T>, Disposable, TimeoutSelectorSupport { - private static final long serialVersionUID = -1957813281749686898L; - final Observer<? super T> actual; - final ObservableSource<U> firstTimeoutIndicator; - final Function<? super T, ? extends ObservableSource<V>> itemTimeoutIndicator; - final ObservableSource<? extends T> other; - final ObserverFullArbiter<T> arbiter; + private static final long serialVersionUID = -7508389464265974549L; - Disposable s; + final Observer<? super T> downstream; - boolean done; + final Function<? super T, ? extends ObservableSource<?>> itemTimeoutIndicator; - volatile long index; + final SequentialDisposable task; - TimeoutOtherObserver(Observer<? super T> actual, - ObservableSource<U> firstTimeoutIndicator, - Function<? super T, ? extends ObservableSource<V>> itemTimeoutIndicator, ObservableSource<? extends T> other) { - this.actual = actual; - this.firstTimeoutIndicator = firstTimeoutIndicator; - this.itemTimeoutIndicator = itemTimeoutIndicator; - this.other = other; - this.arbiter = new ObserverFullArbiter<T>(actual, this, 8); - } - - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + final AtomicLong index; - arbiter.setDisposable(s); + final AtomicReference<Disposable> upstream; - Observer<? super T> a = actual; + ObservableSource<? extends T> fallback; - ObservableSource<U> p = firstTimeoutIndicator; - - if (p != null) { - TimeoutInnerObserver<T, U, V> tis = new TimeoutInnerObserver<T, U, V>(this, 0); + TimeoutFallbackObserver(Observer<? super T> actual, + Function<? super T, ? extends ObservableSource<?>> itemTimeoutIndicator, + ObservableSource<? extends T> fallback) { + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.fallback = fallback; + this.index = new AtomicLong(); + this.upstream = new AtomicReference<Disposable>(); + } - if (compareAndSet(null, tis)) { - a.onSubscribe(arbiter); - p.subscribe(tis); - } - } else { - a.onSubscribe(arbiter); - } - } + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); } @Override public void onNext(T t) { - if (done) { - return; - } - long idx = index + 1; - index = idx; - - if (!arbiter.onNext(t, s)) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { return; } - Disposable d = get(); + Disposable d = task.get(); if (d != null) { d.dispose(); } - ObservableSource<V> p; + downstream.onNext(t); + + ObservableSource<?> itemTimeoutObservableSource; try { - p = ObjectHelper.requireNonNull(itemTimeoutIndicator.apply(t), "The ObservableSource returned is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - actual.onError(e); + itemTimeoutObservableSource = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null ObservableSource."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().dispose(); + index.getAndSet(Long.MAX_VALUE); + downstream.onError(ex); return; } - TimeoutInnerObserver<T, U, V> tis = new TimeoutInnerObserver<T, U, V>(this, idx); + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutObservableSource.subscribe(consumer); + } + } - if (compareAndSet(d, tis)) { - p.subscribe(tis); + void startFirstTimeout(ObservableSource<?> firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } } } @Override public void onError(Throwable t) { - if (done) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + task.dispose(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - dispose(); - arbiter.onError(t, s); } @Override public void onComplete() { - if (done) { - return; + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + task.dispose(); } - done = true; - dispose(); - arbiter.onComplete(s); } @Override - public void dispose() { - if (DisposableHelper.dispose(this)) { - s.dispose(); + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + ObservableSource<? extends T> f = fallback; + fallback = null; + + f.subscribe(new ObservableTimeoutTimed.FallbackObserver<T>(downstream, this)); } } + @Override + public void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(this); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(this); + task.dispose(); + } + @Override public boolean isDisposed() { - return s.isDisposed(); + return DisposableHelper.isDisposed(get()); + } + } + + static final class TimeoutConsumer extends AtomicReference<Disposable> + implements Observer<Object>, Disposable { + + private static final long serialVersionUID = 8708641127342403073L; + + final TimeoutSelectorSupport parent; + + final long idx; + + TimeoutConsumer(long idx, TimeoutSelectorSupport parent) { + this.idx = idx; + this.parent = parent; } @Override - public void timeout(long idx) { - if (idx == index) { - dispose(); - other.subscribe(new FullArbiterObserver<T>(arbiter)); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + Disposable upstream = get(); + if (upstream != DisposableHelper.DISPOSED) { + upstream.dispose(); + lazySet(DisposableHelper.DISPOSED); + parent.onTimeout(idx); } } @Override - public void innerError(Throwable e) { - s.dispose(); - actual.onError(e); + public void onError(Throwable t) { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.onTimeoutError(idx, t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.onTimeout(idx); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(this.get()); } } + } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java index 0c152df34d..9e3cb8a945 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java @@ -14,26 +14,22 @@ package io.reactivex.internal.operators.observable; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; import io.reactivex.*; -import io.reactivex.Scheduler.Worker; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.*; -import io.reactivex.internal.observers.FullArbiterObserver; -import io.reactivex.observers.SerializedObserver; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + public final class ObservableTimeoutTimed<T> extends AbstractObservableWithUpstream<T, T> { final long timeout; final TimeUnit unit; final Scheduler scheduler; final ObservableSource<? extends T> other; - - static final Disposable NEW_TIMER = new EmptyDisposable(); - - public ObservableTimeoutTimed(ObservableSource<T> source, + public ObservableTimeoutTimed(Observable<T> source, long timeout, TimeUnit unit, Scheduler scheduler, ObservableSource<? extends T> other) { super(source); this.timeout = timeout; @@ -43,265 +39,275 @@ public ObservableTimeoutTimed(ObservableSource<T> source, } @Override - public void subscribeActual(Observer<? super T> t) { + protected void subscribeActual(Observer<? super T> observer) { if (other == null) { - source.subscribe(new TimeoutTimedObserver<T>( - new SerializedObserver<T>(t), // because errors can race - timeout, unit, scheduler.createWorker())); + TimeoutObserver<T> parent = new TimeoutObserver<T>(observer, timeout, unit, scheduler.createWorker()); + observer.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); } else { - source.subscribe(new TimeoutTimedOtherObserver<T>( - t, // the FullArbiter serializes - timeout, unit, scheduler.createWorker(), other)); + TimeoutFallbackObserver<T> parent = new TimeoutFallbackObserver<T>(observer, timeout, unit, scheduler.createWorker(), other); + observer.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); } } - static final class TimeoutTimedOtherObserver<T> - extends AtomicReference<Disposable> implements Observer<T>, Disposable { - private static final long serialVersionUID = -4619702551964128179L; + static final class TimeoutObserver<T> extends AtomicLong + implements Observer<T>, Disposable, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Observer<? super T> downstream; - final Observer<? super T> actual; final long timeout; - final TimeUnit unit; - final Scheduler.Worker worker; - final ObservableSource<? extends T> other; - Disposable s; + final TimeUnit unit; - final ObserverFullArbiter<T> arbiter; + final Scheduler.Worker worker; - volatile long index; + final SequentialDisposable task; - volatile boolean done; + final AtomicReference<Disposable> upstream; - TimeoutTimedOtherObserver(Observer<? super T> actual, long timeout, TimeUnit unit, Worker worker, - ObservableSource<? extends T> other) { - this.actual = actual; + TimeoutObserver(Observer<? super T> actual, long timeout, TimeUnit unit, Scheduler.Worker worker) { + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; - this.other = other; - this.arbiter = new ObserverFullArbiter<T>(actual, this, 8); + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference<Disposable>(); } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - if (arbiter.setDisposable(s)) { - actual.onSubscribe(arbiter); - - scheduleTimeout(0L); - } - } - + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); } @Override public void onNext(T t) { - if (done) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { return; } - long idx = index + 1; - index = idx; - if (arbiter.onNext(t, s)) { - scheduleTimeout(idx); - } - } - - void scheduleTimeout(final long idx) { - Disposable d = get(); - if (d != null) { - d.dispose(); - } + task.get().dispose(); - if (compareAndSet(d, NEW_TIMER)) { - d = worker.schedule(new SubscribeNext(idx), timeout, unit); + downstream.onNext(t); - DisposableHelper.replace(this, d); - } + startTimeout(idx + 1); } - void subscribeNext() { - other.subscribe(new FullArbiterObserver<T>(arbiter)); + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); } @Override public void onError(Throwable t) { - if (done) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - arbiter.onError(t, s); - worker.dispose(); } @Override public void onComplete() { - if (done) { - return; + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + + worker.dispose(); } - done = true; - arbiter.onComplete(s); - worker.dispose(); } @Override public void dispose() { - s.dispose(); + DisposableHelper.dispose(upstream); worker.dispose(); } @Override public boolean isDisposed() { - return worker.isDisposed(); + return DisposableHelper.isDisposed(upstream.get()); } + } - final class SubscribeNext implements Runnable { - private final long idx; + static final class TimeoutTask implements Runnable { - SubscribeNext(long idx) { - this.idx = idx; - } + final TimeoutSupport parent; - @Override - public void run() { - if (idx == index) { - done = true; - s.dispose(); - DisposableHelper.dispose(TimeoutTimedOtherObserver.this); + final long idx; - subscribeNext(); + TimeoutTask(long idx, TimeoutSupport parent) { + this.idx = idx; + this.parent = parent; + } - worker.dispose(); - } - } + @Override + public void run() { + parent.onTimeout(idx); } } - static final class TimeoutTimedObserver<T> - extends AtomicReference<Disposable> - implements Observer<T>, Disposable { - private static final long serialVersionUID = -8387234228317808253L; + static final class TimeoutFallbackObserver<T> extends AtomicReference<Disposable> + implements Observer<T>, Disposable, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Observer<? super T> downstream; - final Observer<? super T> actual; final long timeout; + final TimeUnit unit; + final Scheduler.Worker worker; - Disposable s; + final SequentialDisposable task; - volatile long index; + final AtomicLong index; - volatile boolean done; + final AtomicReference<Disposable> upstream; - TimeoutTimedObserver(Observer<? super T> actual, long timeout, TimeUnit unit, Worker worker) { - this.actual = actual; + ObservableSource<? extends T> fallback; + + TimeoutFallbackObserver(Observer<? super T> actual, long timeout, TimeUnit unit, + Scheduler.Worker worker, ObservableSource<? extends T> fallback) { + this.downstream = actual; this.timeout = timeout; this.unit = unit; this.worker = worker; + this.fallback = fallback; + this.task = new SequentialDisposable(); + this.index = new AtomicLong(); + this.upstream = new AtomicReference<Disposable>(); } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - scheduleTimeout(0L); - } - + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); } @Override public void onNext(T t) { - if (done) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { return; } - long idx = index + 1; - index = idx; - actual.onNext(t); - - scheduleTimeout(idx); - } + task.get().dispose(); - void scheduleTimeout(final long idx) { - Disposable d = get(); - if (d != null) { - d.dispose(); - } + downstream.onNext(t); - if (compareAndSet(d, NEW_TIMER)) { - d = worker.schedule(new TimeoutTask(idx), timeout, unit); + startTimeout(idx + 1); + } - DisposableHelper.replace(this, d); - } + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); } @Override public void onError(Throwable t) { - if (done) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { RxJavaPlugins.onError(t); - return; } - done = true; - - actual.onError(t); - dispose(); } @Override public void onComplete() { - if (done) { - return; + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); } - done = true; + } + + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + ObservableSource<? extends T> f = fallback; + fallback = null; + + f.subscribe(new FallbackObserver<T>(downstream, this)); - actual.onComplete(); - dispose(); + worker.dispose(); + } } @Override public void dispose() { - s.dispose(); + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(this); worker.dispose(); } @Override public boolean isDisposed() { - return worker.isDisposed(); + return DisposableHelper.isDisposed(get()); } + } - final class TimeoutTask implements Runnable { - private final long idx; + static final class FallbackObserver<T> implements Observer<T> { - TimeoutTask(long idx) { - this.idx = idx; - } + final Observer<? super T> downstream; - @Override - public void run() { - if (idx == index) { - done = true; - s.dispose(); - DisposableHelper.dispose(TimeoutTimedObserver.this); + final AtomicReference<Disposable> arbiter; - actual.onError(new TimeoutException()); + FallbackObserver(Observer<? super T> actual, AtomicReference<Disposable> arbiter) { + this.downstream = actual; + this.arbiter = arbiter; + } - worker.dispose(); - } - } + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(arbiter, d); } - } - static final class EmptyDisposable implements Disposable { @Override - public void dispose() { } + public void onNext(T t) { + downstream.onNext(t); + } @Override - public boolean isDisposed() { - return true; + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); } } + + interface TimeoutSupport { + + void onTimeout(long idx); + + } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java index 1414da06f6..c635ccb9db 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java @@ -31,9 +31,9 @@ public ObservableTimer(long delay, TimeUnit unit, Scheduler scheduler) { } @Override - public void subscribeActual(Observer<? super Long> s) { - TimerObserver ios = new TimerObserver(s); - s.onSubscribe(ios); + public void subscribeActual(Observer<? super Long> observer) { + TimerObserver ios = new TimerObserver(observer); + observer.onSubscribe(ios); Disposable d = scheduler.scheduleDirect(ios, delay, unit); @@ -45,10 +45,10 @@ static final class TimerObserver extends AtomicReference<Disposable> private static final long serialVersionUID = -2809475196591179431L; - final Observer<? super Long> actual; + final Observer<? super Long> downstream; - TimerObserver(Observer<? super Long> actual) { - this.actual = actual; + TimerObserver(Observer<? super Long> downstream) { + this.downstream = downstream; } @Override @@ -64,9 +64,9 @@ public boolean isDisposed() { @Override public void run() { if (!isDisposed()) { - actual.onNext(0L); + downstream.onNext(0L); lazySet(EmptyDisposable.INSTANCE); - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java index 8181c00563..afeee5399d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java @@ -52,36 +52,35 @@ public void subscribeActual(Observer<? super U> t) { } static final class ToListObserver<T, U extends Collection<? super T>> implements Observer<T>, Disposable { - U collection; - final Observer<? super U> actual; + final Observer<? super U> downstream; + + Disposable upstream; - Disposable s; + U collection; ToListObserver(Observer<? super U> actual, U collection) { - this.actual = actual; + this.downstream = actual; this.collection = collection; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { collection.add(t); @@ -90,15 +89,15 @@ public void onNext(T t) { @Override public void onError(Throwable t) { collection = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { U c = collection; collection = null; - actual.onNext(c); - actual.onComplete(); + downstream.onNext(c); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java index dc38c76dfd..410af1e161 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java @@ -64,37 +64,35 @@ public Observable<U> fuseToObservable() { } static final class ToListObserver<T, U extends Collection<? super T>> implements Observer<T>, Disposable { - final SingleObserver<? super U> actual; + final SingleObserver<? super U> downstream; U collection; - Disposable s; + Disposable upstream; ToListObserver(SingleObserver<? super U> actual, U collection) { - this.actual = actual; + this.downstream = actual; this.collection = collection; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { collection.add(t); @@ -103,14 +101,14 @@ public void onNext(T t) { @Override public void onError(Throwable t) { collection = null; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { U c = collection; collection = null; - actual.onSuccess(c); + downstream.onSuccess(c); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java index bfcc33308e..5f4ecad8a6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java @@ -36,28 +36,28 @@ static final class UnsubscribeObserver<T> extends AtomicBoolean implements Obser private static final long serialVersionUID = 1015244841293359600L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final Scheduler scheduler; - Disposable s; + Disposable upstream; UnsubscribeObserver(Observer<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!get()) { - actual.onNext(t); + downstream.onNext(t); } } @@ -67,13 +67,13 @@ public void onError(Throwable t) { RxJavaPlugins.onError(t); return; } - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!get()) { - actual.onComplete(); + downstream.onComplete(); } } @@ -92,7 +92,7 @@ public boolean isDisposed() { final class DisposeTask implements Runnable { @Override public void run() { - s.dispose(); + upstream.dispose(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java index e29c3e739e..46806ae0dc 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java @@ -41,14 +41,14 @@ public ObservableUsing(Callable<? extends D> resourceSupplier, } @Override - public void subscribeActual(Observer<? super T> s) { + public void subscribeActual(Observer<? super T> observer) { D resource; try { resource = resourceSupplier.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } @@ -61,14 +61,14 @@ public void subscribeActual(Observer<? super T> s) { disposer.accept(resource); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptyDisposable.error(new CompositeException(e, ex), s); + EmptyDisposable.error(new CompositeException(e, ex), observer); return; } - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - UsingObserver<T, D> us = new UsingObserver<T, D>(s, resource, disposer, eager); + UsingObserver<T, D> us = new UsingObserver<T, D>(observer, resource, disposer, eager); source.subscribe(us); } @@ -77,31 +77,31 @@ static final class UsingObserver<T, D> extends AtomicBoolean implements Observer private static final long serialVersionUID = 5904473792286235046L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final D resource; final Consumer<? super D> disposer; final boolean eager; - Disposable s; + Disposable upstream; UsingObserver(Observer<? super T> actual, D resource, Consumer<? super D> disposer, boolean eager) { - this.actual = actual; + this.downstream = actual; this.resource = resource; this.disposer = disposer; this.eager = eager; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override @@ -116,11 +116,11 @@ public void onError(Throwable t) { } } - s.dispose(); - actual.onError(t); + upstream.dispose(); + downstream.onError(t); } else { - actual.onError(t); - s.dispose(); + downstream.onError(t); + upstream.dispose(); disposeAfter(); } } @@ -133,16 +133,16 @@ public void onComplete() { disposer.accept(resource); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } } - s.dispose(); - actual.onComplete(); + upstream.dispose(); + downstream.onComplete(); } else { - actual.onComplete(); - s.dispose(); + downstream.onComplete(); + upstream.dispose(); disposeAfter(); } } @@ -150,7 +150,7 @@ public void onComplete() { @Override public void dispose() { disposeAfter(); - s.dispose(); + upstream.dispose(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java index dae684bc9a..0c5c50d407 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java @@ -47,30 +47,30 @@ static final class WindowExactObserver<T> implements Observer<T>, Disposable, Runnable { private static final long serialVersionUID = -7481782523886138128L; - final Observer<? super Observable<T>> actual; + final Observer<? super Observable<T>> downstream; final long count; final int capacityHint; long size; - Disposable s; + Disposable upstream; UnicastSubject<T> window; volatile boolean cancelled; WindowExactObserver(Observer<? super Observable<T>> actual, long count, int capacityHint) { - this.actual = actual; + this.downstream = actual; this.count = count; this.capacityHint = capacityHint; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -80,7 +80,7 @@ public void onNext(T t) { if (w == null && !cancelled) { w = UnicastSubject.create(capacityHint, this); window = w; - actual.onNext(w); + downstream.onNext(w); } if (w != null) { @@ -90,7 +90,7 @@ public void onNext(T t) { window = null; w.onComplete(); if (cancelled) { - s.dispose(); + upstream.dispose(); } } } @@ -103,7 +103,7 @@ public void onError(Throwable t) { window = null; w.onError(t); } - actual.onError(t); + downstream.onError(t); } @Override @@ -113,7 +113,7 @@ public void onComplete() { window = null; w.onComplete(); } - actual.onComplete(); + downstream.onComplete(); } @Override @@ -129,7 +129,7 @@ public boolean isDisposed() { @Override public void run() { if (cancelled) { - s.dispose(); + upstream.dispose(); } } } @@ -138,7 +138,7 @@ static final class WindowSkipObserver<T> extends AtomicBoolean implements Observer<T>, Disposable, Runnable { private static final long serialVersionUID = 3366976432059579510L; - final Observer<? super Observable<T>> actual; + final Observer<? super Observable<T>> downstream; final long count; final long skip; final int capacityHint; @@ -151,12 +151,12 @@ static final class WindowSkipObserver<T> extends AtomicBoolean /** Counts how many elements were emitted to the very first window in windows. */ long firstEmission; - Disposable s; + Disposable upstream; final AtomicInteger wip = new AtomicInteger(); WindowSkipObserver(Observer<? super Observable<T>> actual, long count, long skip, int capacityHint) { - this.actual = actual; + this.downstream = actual; this.count = count; this.skip = skip; this.capacityHint = capacityHint; @@ -164,11 +164,11 @@ static final class WindowSkipObserver<T> extends AtomicBoolean } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -184,7 +184,7 @@ public void onNext(T t) { wip.getAndIncrement(); UnicastSubject<T> w = UnicastSubject.create(capacityHint, this); ws.offer(w); - actual.onNext(w); + downstream.onNext(w); } long c = firstEmission + 1; @@ -196,7 +196,7 @@ public void onNext(T t) { if (c >= count) { ws.poll().onComplete(); if (ws.isEmpty() && cancelled) { - this.s.dispose(); + this.upstream.dispose(); return; } firstEmission = c - s; @@ -213,7 +213,7 @@ public void onError(Throwable t) { while (!ws.isEmpty()) { ws.poll().onError(t); } - actual.onError(t); + downstream.onError(t); } @Override @@ -222,7 +222,7 @@ public void onComplete() { while (!ws.isEmpty()) { ws.poll().onComplete(); } - actual.onComplete(); + downstream.onComplete(); } @Override @@ -239,7 +239,7 @@ public boolean isDisposed() { public void run() { if (wip.decrementAndGet() == 0) { if (cancelled) { - s.dispose(); + upstream.dispose(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java index d78570ee22..a0fb1f9f10 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java @@ -18,167 +18,199 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.DisposableHelper; -import io.reactivex.internal.observers.QueueDrainObserver; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.util.NotificationLite; -import io.reactivex.observers.*; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.observers.DisposableObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.UnicastSubject; public final class ObservableWindowBoundary<T, B> extends AbstractObservableWithUpstream<T, Observable<T>> { final ObservableSource<B> other; - final int bufferSize; + final int capacityHint; - public ObservableWindowBoundary(ObservableSource<T> source, ObservableSource<B> other, int bufferSize) { + public ObservableWindowBoundary(ObservableSource<T> source, ObservableSource<B> other, int capacityHint) { super(source); this.other = other; - this.bufferSize = bufferSize; + this.capacityHint = capacityHint; } @Override - public void subscribeActual(Observer<? super Observable<T>> t) { - source.subscribe(new WindowBoundaryMainObserver<T, B>(new SerializedObserver<Observable<T>>(t), other, bufferSize)); + public void subscribeActual(Observer<? super Observable<T>> observer) { + WindowBoundaryMainObserver<T, B> parent = new WindowBoundaryMainObserver<T, B>(observer, capacityHint); + + observer.onSubscribe(parent); + other.subscribe(parent.boundaryObserver); + + source.subscribe(parent); } static final class WindowBoundaryMainObserver<T, B> - extends QueueDrainObserver<T, Object, Observable<T>> - implements Disposable { + extends AtomicInteger + implements Observer<T>, Disposable, Runnable { - final ObservableSource<B> other; - final int bufferSize; + private static final long serialVersionUID = 2233020065421370272L; - Disposable s; + final Observer<? super Observable<T>> downstream; - final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); + final int capacityHint; - UnicastSubject<T> window; + final WindowBoundaryInnerObserver<T, B> boundaryObserver; - static final Object NEXT = new Object(); + final AtomicReference<Disposable> upstream; - final AtomicLong windows = new AtomicLong(); + final AtomicInteger windows; - WindowBoundaryMainObserver(Observer<? super Observable<T>> actual, ObservableSource<B> other, - int bufferSize) { - super(actual, new MpscLinkedQueue<Object>()); - this.other = other; - this.bufferSize = bufferSize; - windows.lazySet(1); - } + final MpscLinkedQueue<Object> queue; - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + final AtomicThrowable errors; - Observer<? super Observable<T>> a = actual; - a.onSubscribe(this); + final AtomicBoolean stopWindows; - if (cancelled) { - return; - } + static final Object NEXT_WINDOW = new Object(); - UnicastSubject<T> w = UnicastSubject.create(bufferSize); + volatile boolean done; - window = w; + UnicastSubject<T> window; - a.onNext(w); + WindowBoundaryMainObserver(Observer<? super Observable<T>> downstream, int capacityHint) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundaryObserver = new WindowBoundaryInnerObserver<T, B>(this); + this.upstream = new AtomicReference<Disposable>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue<Object>(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + } - WindowBoundaryInnerObserver<T, B> inner = new WindowBoundaryInnerObserver<T, B>(this); + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(upstream, d)) { - if (boundary.compareAndSet(null, inner)) { - windows.getAndIncrement(); - other.subscribe(inner); - } + innerNext(); } } @Override public void onNext(T t) { - if (fastEnter()) { - UnicastSubject<T> w = window; - - w.onNext(t); + queue.offer(t); + drain(); + } - if (leave(-1) == 0) { - return; - } + @Override + public void onError(Throwable e) { + boundaryObserver.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); } else { - queue.offer(NotificationLite.next(t)); - if (!enter()) { - return; - } + RxJavaPlugins.onError(e); } - drainLoop(); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - error = t; + public void onComplete() { + boundaryObserver.dispose(); done = true; - if (enter()) { - drainLoop(); - } + drain(); + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + @Override + public void dispose() { + if (stopWindows.compareAndSet(false, true)) { + boundaryObserver.dispose(); + if (windows.decrementAndGet() == 0) { + DisposableHelper.dispose(upstream); + } } - - actual.onError(t); } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - if (enter()) { - drainLoop(); - } + public boolean isDisposed() { + return stopWindows.get(); + } + @Override + public void run() { if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + DisposableHelper.dispose(upstream); } + } - actual.onComplete(); - + void innerNext() { + queue.offer(NEXT_WINDOW); + drain(); } - @Override - public void dispose() { - cancelled = true; + void innerError(Throwable e) { + DisposableHelper.dispose(upstream); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } } - @Override - public boolean isDisposed() { - return cancelled; + void innerComplete() { + DisposableHelper.dispose(upstream); + done = true; + drain(); } - void drainLoop() { - final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; - UnicastSubject<T> w = window; + Observer<? super Observable<T>> downstream = this.downstream; + MpscLinkedQueue<Object> queue = this.queue; + AtomicThrowable errors = this.errors; + for (;;) { for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastSubject<T> w = window; + boolean d = done; - Object o = q.poll(); + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); - boolean empty = o == null; + boolean empty = v == null; if (d && empty) { - DisposableHelper.dispose(boundary); - Throwable e = error; - if (e != null) { - w.onError(e); + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); } else { - w.onComplete(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); } return; } @@ -187,48 +219,35 @@ void drainLoop() { break; } - if (o == NEXT) { - w.onComplete(); - - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); - return; - } - - if (cancelled) { - continue; - } - - w = UnicastSubject.create(bufferSize); + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } - windows.getAndIncrement(); + if (w != null) { + window = null; + w.onComplete(); + } + if (!stopWindows.get()) { + w = UnicastSubject.create(capacityHint, this); window = w; + windows.getAndIncrement(); - a.onNext(w); - - continue; + downstream.onNext(w); } - - w.onNext(NotificationLite.<T>getValue(o)); } - missed = leave(-missed); + missed = addAndGet(-missed); if (missed == 0) { - return; + break; } } } - - void next() { - queue.offer(NEXT); - if (enter()) { - drainLoop(); - } - } } static final class WindowBoundaryInnerObserver<T, B> extends DisposableObserver<B> { + final WindowBoundaryMainObserver<T, B> parent; boolean done; @@ -242,7 +261,7 @@ public void onNext(B t) { if (done) { return; } - parent.next(); + parent.innerNext(); } @Override @@ -252,7 +271,7 @@ public void onError(Throwable t) { return; } done = true; - parent.onError(t); + parent.innerError(t); } @Override @@ -261,7 +280,7 @@ public void onComplete() { return; } done = true; - parent.onComplete(); + parent.innerComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java index eec057bf22..d8e745e213 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java @@ -61,7 +61,7 @@ static final class WindowBoundaryMainObserver<T, B, V> final int bufferSize; final CompositeDisposable resources; - Disposable s; + Disposable upstream; final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); @@ -69,6 +69,8 @@ static final class WindowBoundaryMainObserver<T, B, V> final AtomicLong windows = new AtomicLong(); + final AtomicBoolean stopWindows = new AtomicBoolean(); + WindowBoundaryMainObserver(Observer<? super Observable<T>> actual, ObservableSource<B> open, Function<? super B, ? extends ObservableSource<V>> close, int bufferSize) { super(actual, new MpscLinkedQueue<Object>()); @@ -81,20 +83,19 @@ static final class WindowBoundaryMainObserver<T, B, V> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); - if (cancelled) { + if (stopWindows.get()) { return; } OperatorWindowBoundaryOpenObserver<T, B> os = new OperatorWindowBoundaryOpenObserver<T, B>(this); if (boundary.compareAndSet(null, os)) { - windows.getAndIncrement(); open.subscribe(os); } } @@ -135,7 +136,7 @@ public void onError(Throwable t) { resources.dispose(); } - actual.onError(t); + downstream.onError(t); } @Override @@ -153,23 +154,28 @@ public void onComplete() { resources.dispose(); } - actual.onComplete(); + downstream.onComplete(); } void error(Throwable t) { - s.dispose(); + upstream.dispose(); resources.dispose(); onError(t); } @Override public void dispose() { - cancelled = true; + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } + } } @Override public boolean isDisposed() { - return cancelled; + return stopWindows.get(); } void disposeBoundary() { @@ -179,7 +185,7 @@ void disposeBoundary() { void drainLoop() { final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + final Observer<? super Observable<T>> a = downstream; final List<UnicastSubject<T>> ws = this.ws; int missed = 1; @@ -229,11 +235,10 @@ void drainLoop() { continue; } - if (cancelled) { + if (stopWindows.get()) { continue; } - w = UnicastSubject.create(bufferSize); ws.add(w); @@ -245,7 +250,7 @@ void drainLoop() { p = ObjectHelper.requireNonNull(close.apply(wo.open), "The ObservableSource supplied is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - cancelled = true; + stopWindows.set(true); a.onError(e); continue; } @@ -338,12 +343,8 @@ static final class OperatorWindowBoundaryCloseObserver<T, V> extends DisposableO @Override public void onNext(V t) { - if (done) { - return; - } - done = true; dispose(); - parent.close(this); + onComplete(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java index f47d096556..c2d319032b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java @@ -21,179 +21,213 @@ import io.reactivex.exceptions.Exceptions; import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.observers.QueueDrainObserver; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.util.NotificationLite; -import io.reactivex.observers.*; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.observers.DisposableObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.UnicastSubject; public final class ObservableWindowBoundarySupplier<T, B> extends AbstractObservableWithUpstream<T, Observable<T>> { final Callable<? extends ObservableSource<B>> other; - final int bufferSize; + final int capacityHint; public ObservableWindowBoundarySupplier( ObservableSource<T> source, - Callable<? extends ObservableSource<B>> other, int bufferSize) { + Callable<? extends ObservableSource<B>> other, int capacityHint) { super(source); this.other = other; - this.bufferSize = bufferSize; + this.capacityHint = capacityHint; } @Override - public void subscribeActual(Observer<? super Observable<T>> t) { - source.subscribe(new WindowBoundaryMainObserver<T, B>(new SerializedObserver<Observable<T>>(t), other, bufferSize)); + public void subscribeActual(Observer<? super Observable<T>> observer) { + WindowBoundaryMainObserver<T, B> parent = new WindowBoundaryMainObserver<T, B>(observer, capacityHint, other); + + source.subscribe(parent); } static final class WindowBoundaryMainObserver<T, B> - extends QueueDrainObserver<T, Object, Observable<T>> - implements Disposable { + extends AtomicInteger + implements Observer<T>, Disposable, Runnable { - final Callable<? extends ObservableSource<B>> other; - final int bufferSize; + private static final long serialVersionUID = 2233020065421370272L; - Disposable s; + final Observer<? super Observable<T>> downstream; - final AtomicReference<Disposable> boundary = new AtomicReference<Disposable>(); + final int capacityHint; - UnicastSubject<T> window; + final AtomicReference<WindowBoundaryInnerObserver<T, B>> boundaryObserver; - static final Object NEXT = new Object(); + static final WindowBoundaryInnerObserver<Object, Object> BOUNDARY_DISPOSED = new WindowBoundaryInnerObserver<Object, Object>(null); - final AtomicLong windows = new AtomicLong(); + final AtomicInteger windows; - WindowBoundaryMainObserver(Observer<? super Observable<T>> actual, Callable<? extends ObservableSource<B>> other, - int bufferSize) { - super(actual, new MpscLinkedQueue<Object>()); - this.other = other; - this.bufferSize = bufferSize; - windows.lazySet(1); - } + final MpscLinkedQueue<Object> queue; - @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + final AtomicThrowable errors; - Observer<? super Observable<T>> a = actual; - a.onSubscribe(this); + final AtomicBoolean stopWindows; - if (cancelled) { - return; - } + final Callable<? extends ObservableSource<B>> other; - ObservableSource<B> p; + static final Object NEXT_WINDOW = new Object(); - try { - p = ObjectHelper.requireNonNull(other.call(), "The first window ObservableSource supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - s.dispose(); - a.onError(e); - return; - } + Disposable upstream; - UnicastSubject<T> w = UnicastSubject.create(bufferSize); + volatile boolean done; - window = w; - - a.onNext(w); + UnicastSubject<T> window; - WindowBoundaryInnerObserver<T, B> inner = new WindowBoundaryInnerObserver<T, B>(this); + WindowBoundaryMainObserver(Observer<? super Observable<T>> downstream, int capacityHint, Callable<? extends ObservableSource<B>> other) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundaryObserver = new AtomicReference<WindowBoundaryInnerObserver<T, B>>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue<Object>(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.other = other; + } - if (boundary.compareAndSet(null, inner)) { - windows.getAndIncrement(); - p.subscribe(inner); - } + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + queue.offer(NEXT_WINDOW); + drain(); } } @Override public void onNext(T t) { - if (fastEnter()) { - UnicastSubject<T> w = window; - - w.onNext(t); + queue.offer(t); + drain(); + } - if (leave(-1) == 0) { - return; - } + @Override + public void onError(Throwable e) { + disposeBoundary(); + if (errors.addThrowable(e)) { + done = true; + drain(); } else { - queue.offer(NotificationLite.next(t)); - if (!enter()) { - return; - } + RxJavaPlugins.onError(e); } - drainLoop(); } @Override - public void onError(Throwable t) { - if (done) { - RxJavaPlugins.onError(t); - return; - } - error = t; + public void onComplete() { + disposeBoundary(); done = true; - if (enter()) { - drainLoop(); - } + drain(); + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + @Override + public void dispose() { + if (stopWindows.compareAndSet(false, true)) { + disposeBoundary(); + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } } + } - actual.onError(t); + @SuppressWarnings({ "rawtypes", "unchecked" }) + void disposeBoundary() { + Disposable d = boundaryObserver.getAndSet((WindowBoundaryInnerObserver)BOUNDARY_DISPOSED); + if (d != null && d != BOUNDARY_DISPOSED) { + d.dispose(); + } } @Override - public void onComplete() { - if (done) { - return; - } - done = true; - if (enter()) { - drainLoop(); - } + public boolean isDisposed() { + return stopWindows.get(); + } + @Override + public void run() { if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); + upstream.dispose(); } + } - actual.onComplete(); - + void innerNext(WindowBoundaryInnerObserver<T, B> sender) { + boundaryObserver.compareAndSet(sender, null); + queue.offer(NEXT_WINDOW); + drain(); } - @Override - public void dispose() { - cancelled = true; + void innerError(Throwable e) { + upstream.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } } - @Override - public boolean isDisposed() { - return cancelled; + void innerComplete() { + upstream.dispose(); + done = true; + drain(); } - void drainLoop() { - final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; - UnicastSubject<T> w = window; + Observer<? super Observable<T>> downstream = this.downstream; + MpscLinkedQueue<Object> queue = this.queue; + AtomicThrowable errors = this.errors; + for (;;) { for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastSubject<T> w = window; + boolean d = done; - Object o = q.poll(); - boolean empty = o == null; + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); + + boolean empty = v == null; if (d && empty) { - DisposableHelper.dispose(boundary); - Throwable e = error; - if (e != null) { - w.onError(e); + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); } else { - w.onComplete(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); } return; } @@ -202,62 +236,48 @@ void drainLoop() { break; } - if (o == NEXT) { - w.onComplete(); + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } - if (windows.decrementAndGet() == 0) { - DisposableHelper.dispose(boundary); - return; - } + if (w != null) { + window = null; + w.onComplete(); + } - if (cancelled) { - continue; - } + if (!stopWindows.get()) { + w = UnicastSubject.create(capacityHint, this); + window = w; + windows.getAndIncrement(); - ObservableSource<B> p; + ObservableSource<B> otherSource; try { - p = ObjectHelper.requireNonNull(other.call(), "The ObservableSource supplied is null"); - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - DisposableHelper.dispose(boundary); - a.onError(e); - return; + otherSource = ObjectHelper.requireNonNull(other.call(), "The other Callable returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + done = true; + continue; } - w = UnicastSubject.create(bufferSize); + WindowBoundaryInnerObserver<T, B> bo = new WindowBoundaryInnerObserver<T, B>(this); - windows.getAndIncrement(); + if (boundaryObserver.compareAndSet(null, bo)) { + otherSource.subscribe(bo); - window = w; - - a.onNext(w); - - WindowBoundaryInnerObserver<T, B> b = new WindowBoundaryInnerObserver<T, B>(this); - - if (boundary.compareAndSet(boundary.get(), b)) { - p.subscribe(b); + downstream.onNext(w); } - - continue; } - - w.onNext(NotificationLite.<T>getValue(o)); } - missed = leave(-missed); + missed = addAndGet(-missed); if (missed == 0) { - return; + break; } } } - - void next() { - queue.offer(NEXT); - if (enter()) { - drainLoop(); - } - } } static final class WindowBoundaryInnerObserver<T, B> extends DisposableObserver<B> { @@ -276,7 +296,7 @@ public void onNext(B t) { } done = true; dispose(); - parent.next(); + parent.innerNext(this); } @Override @@ -286,7 +306,7 @@ public void onError(Throwable t) { return; } done = true; - parent.onError(t); + parent.innerError(t); } @Override @@ -295,8 +315,7 @@ public void onComplete() { return; } done = true; - parent.onComplete(); -// parent.next(); + parent.innerComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java index 73676bf698..5214d2b225 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java @@ -15,14 +15,13 @@ import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.Scheduler.Worker; import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.disposables.*; import io.reactivex.internal.observers.QueueDrainObserver; import io.reactivex.internal.queue.MpscLinkedQueue; import io.reactivex.internal.util.NotificationLite; @@ -81,11 +80,11 @@ static final class WindowExactUnboundedObserver<T> final Scheduler scheduler; final int bufferSize; - Disposable s; + Disposable upstream; UnicastSubject<T> window; - final AtomicReference<Disposable> timer = new AtomicReference<Disposable>(); + final SequentialDisposable timer = new SequentialDisposable(); static final Object NEXT = new Object(); @@ -101,20 +100,20 @@ static final class WindowExactUnboundedObserver<T> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; window = UnicastSubject.<T>create(bufferSize); - Observer<? super Observable<T>> a = actual; + Observer<? super Observable<T>> a = downstream; a.onSubscribe(this); a.onNext(window); if (!cancelled) { - Disposable d = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); - DisposableHelper.replace(timer, d); + Disposable task = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); + timer.replace(task); } } } @@ -146,8 +145,7 @@ public void onError(Throwable t) { drainLoop(); } - disposeTimer(); - actual.onError(t); + downstream.onError(t); } @Override @@ -157,8 +155,7 @@ public void onComplete() { drainLoop(); } - disposeTimer(); - actual.onComplete(); + downstream.onComplete(); } @Override @@ -171,15 +168,10 @@ public boolean isDisposed() { return cancelled; } - void disposeTimer() { - DisposableHelper.dispose(timer); - } - @Override public void run() { if (cancelled) { terminated = true; - disposeTimer(); } queue.offer(NEXT); if (enter()) { @@ -190,7 +182,7 @@ public void run() { void drainLoop() { final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + final Observer<? super Observable<T>> a = downstream; UnicastSubject<T> w = window; int missed = 1; @@ -206,13 +198,13 @@ void drainLoop() { if (d && (o == null || o == NEXT)) { window = null; q.clear(); - disposeTimer(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + timer.dispose(); return; } @@ -228,7 +220,7 @@ void drainLoop() { a.onNext(w); } else { - s.dispose(); + upstream.dispose(); } continue; } @@ -260,14 +252,13 @@ static final class WindowExactBoundedObserver<T> long producerIndex; - Disposable s; + Disposable upstream; UnicastSubject<T> window; - volatile boolean terminated; - final AtomicReference<Disposable> timer = new AtomicReference<Disposable>(); + final SequentialDisposable timer = new SequentialDisposable(); WindowExactBoundedObserver( Observer<? super Observable<T>> actual, @@ -288,11 +279,11 @@ static final class WindowExactBoundedObserver<T> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - Observer<? super Observable<T>> a = actual; + Observer<? super Observable<T>> a = downstream; a.onSubscribe(this); @@ -305,15 +296,15 @@ public void onSubscribe(Disposable s) { a.onNext(w); - Disposable d; + Disposable task; ConsumerIndexHolder consumerIndexHolder = new ConsumerIndexHolder(producerIndex, this); if (restartTimerOnMaxSize) { - d = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); + task = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); } else { - d = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); + task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); } - DisposableHelper.replace(timer, d); + timer.replace(task); } } @@ -337,7 +328,7 @@ public void onNext(T t) { w = UnicastSubject.create(bufferSize); window = w; - actual.onNext(w); + downstream.onNext(w); if (restartTimerOnMaxSize) { Disposable tm = timer.get(); tm.dispose(); @@ -370,8 +361,7 @@ public void onError(Throwable t) { drainLoop(); } - actual.onError(t); - disposeTimer(); + downstream.onError(t); } @Override @@ -381,8 +371,7 @@ public void onComplete() { drainLoop(); } - actual.onComplete(); - disposeTimer(); + downstream.onComplete(); } @Override @@ -405,7 +394,7 @@ void disposeTimer() { void drainLoop() { final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + final Observer<? super Observable<T>> a = downstream; UnicastSubject<T> w = window; int missed = 1; @@ -413,7 +402,7 @@ void drainLoop() { for (;;) { if (terminated) { - s.dispose(); + upstream.dispose(); q.clear(); disposeTimer(); return; @@ -429,13 +418,13 @@ void drainLoop() { if (d && (empty || isHolder)) { window = null; q.clear(); - disposeTimer(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + disposeTimer(); return; } @@ -445,7 +434,7 @@ void drainLoop() { if (isHolder) { ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; - if (restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { w.onComplete(); count = 0; w = UnicastSubject.create(bufferSize); @@ -467,7 +456,7 @@ void drainLoop() { w = UnicastSubject.create(bufferSize); window = w; - actual.onNext(w); + downstream.onNext(w); if (restartTimerOnMaxSize) { Disposable tm = timer.get(); @@ -508,7 +497,6 @@ public void run() { p.queue.offer(this); } else { p.terminated = true; - p.disposeTimer(); } if (p.enter()) { p.drainLoop(); @@ -528,7 +516,7 @@ static final class WindowSkipObserver<T> final List<UnicastSubject<T>> windows; - Disposable s; + Disposable upstream; volatile boolean terminated; @@ -545,11 +533,11 @@ static final class WindowSkipObserver<T> } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); if (cancelled) { return; @@ -558,7 +546,7 @@ public void onSubscribe(Disposable s) { final UnicastSubject<T> w = UnicastSubject.create(bufferSize); windows.add(w); - actual.onNext(w); + downstream.onNext(w); worker.schedule(new CompletionTask(w), timespan, unit); worker.schedulePeriodically(this, timeskip, timeskip, unit); @@ -592,8 +580,7 @@ public void onError(Throwable t) { drainLoop(); } - actual.onError(t); - disposeWorker(); + downstream.onError(t); } @Override @@ -603,8 +590,7 @@ public void onComplete() { drainLoop(); } - actual.onComplete(); - disposeWorker(); + downstream.onComplete(); } @Override @@ -617,10 +603,6 @@ public boolean isDisposed() { return cancelled; } - void disposeWorker() { - worker.dispose(); - } - void complete(UnicastSubject<T> w) { queue.offer(new SubjectWork<T>(w, false)); if (enter()) { @@ -631,7 +613,7 @@ void complete(UnicastSubject<T> w) { @SuppressWarnings("unchecked") void drainLoop() { final MpscLinkedQueue<Object> q = (MpscLinkedQueue<Object>)queue; - final Observer<? super Observable<T>> a = actual; + final Observer<? super Observable<T>> a = downstream; final List<UnicastSubject<T>> ws = windows; int missed = 1; @@ -640,10 +622,10 @@ void drainLoop() { for (;;) { if (terminated) { - s.dispose(); - disposeWorker(); + upstream.dispose(); q.clear(); ws.clear(); + worker.dispose(); return; } @@ -666,8 +648,8 @@ void drainLoop() { w.onComplete(); } } - disposeWorker(); ws.clear(); + worker.dispose(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java index d60a086687..83369a73a7 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java @@ -40,7 +40,7 @@ public void subscribeActual(Observer<? super R> t) { serial.onSubscribe(wlf); - other.subscribe(new WithLastFrom(wlf)); + other.subscribe(new WithLatestFromOtherObserver(wlf)); source.subscribe(wlf); } @@ -49,21 +49,22 @@ static final class WithLatestFromObserver<T, U, R> extends AtomicReference<U> im private static final long serialVersionUID = -312246233408980075L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final BiFunction<? super T, ? super U, ? extends R> combiner; - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); final AtomicReference<Disposable> other = new AtomicReference<Disposable>(); WithLatestFromObserver(Observer<? super R> actual, BiFunction<? super T, ? super U, ? extends R> combiner) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; } + @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override @@ -76,34 +77,34 @@ public void onNext(T t) { } catch (Throwable e) { Exceptions.throwIfFatal(e); dispose(); - actual.onError(e); + downstream.onError(e); return; } - actual.onNext(r); + downstream.onNext(r); } } @Override public void onError(Throwable t) { DisposableHelper.dispose(other); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { DisposableHelper.dispose(other); - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); DisposableHelper.dispose(other); } @Override public boolean isDisposed() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } public boolean setOther(Disposable o) { @@ -111,31 +112,31 @@ public boolean setOther(Disposable o) { } public void otherError(Throwable e) { - DisposableHelper.dispose(s); - actual.onError(e); + DisposableHelper.dispose(upstream); + downstream.onError(e); } } - final class WithLastFrom implements Observer<U> { - private final WithLatestFromObserver<T, U, R> wlf; + final class WithLatestFromOtherObserver implements Observer<U> { + private final WithLatestFromObserver<T, U, R> parent; - WithLastFrom(WithLatestFromObserver<T, U, R> wlf) { - this.wlf = wlf; + WithLatestFromOtherObserver(WithLatestFromObserver<T, U, R> parent) { + this.parent = parent; } @Override - public void onSubscribe(Disposable s) { - wlf.setOther(s); + public void onSubscribe(Disposable d) { + parent.setOther(d); } @Override public void onNext(U t) { - wlf.lazySet(t); + parent.lazySet(t); } @Override public void onError(Throwable t) { - wlf.otherError(t); + parent.otherError(t); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java index 1578d0e950..194d33c61c 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java @@ -59,7 +59,7 @@ public ObservableWithLatestFromMany(@NonNull ObservableSource<T> source, @NonNul } @Override - protected void subscribeActual(Observer<? super R> s) { + protected void subscribeActual(Observer<? super R> observer) { ObservableSource<?>[] others = otherArray; int n = 0; if (others == null) { @@ -74,7 +74,7 @@ protected void subscribeActual(Observer<? super R> s) { } } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptyDisposable.error(ex, s); + EmptyDisposable.error(ex, observer); return; } @@ -83,12 +83,12 @@ protected void subscribeActual(Observer<? super R> s) { } if (n == 0) { - new ObservableMap<T, R>(source, new SingletonArrayFunc()).subscribeActual(s); + new ObservableMap<T, R>(source, new SingletonArrayFunc()).subscribeActual(observer); return; } - WithLatestFromObserver<T, R> parent = new WithLatestFromObserver<T, R>(s, combiner, n); - s.onSubscribe(parent); + WithLatestFromObserver<T, R> parent = new WithLatestFromObserver<T, R>(observer, combiner, n); + observer.onSubscribe(parent); parent.subscribe(others, n); source.subscribe(parent); @@ -100,7 +100,7 @@ static final class WithLatestFromObserver<T, R> private static final long serialVersionUID = 1577321883966341961L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super Object[], R> combiner; @@ -108,14 +108,14 @@ static final class WithLatestFromObserver<T, R> final AtomicReferenceArray<Object> values; - final AtomicReference<Disposable> d; + final AtomicReference<Disposable> upstream; final AtomicThrowable error; volatile boolean done; WithLatestFromObserver(Observer<? super R> actual, Function<? super Object[], R> combiner, int n) { - this.actual = actual; + this.downstream = actual; this.combiner = combiner; WithLatestInnerObserver[] s = new WithLatestInnerObserver[n]; for (int i = 0; i < n; i++) { @@ -123,15 +123,15 @@ static final class WithLatestFromObserver<T, R> } this.observers = s; this.values = new AtomicReferenceArray<Object>(n); - this.d = new AtomicReference<Disposable>(); + this.upstream = new AtomicReference<Disposable>(); this.error = new AtomicThrowable(); } void subscribe(ObservableSource<?>[] others, int n) { WithLatestInnerObserver[] observers = this.observers; - AtomicReference<Disposable> s = this.d; + AtomicReference<Disposable> upstream = this.upstream; for (int i = 0; i < n; i++) { - if (DisposableHelper.isDisposed(s.get()) || done) { + if (DisposableHelper.isDisposed(upstream.get()) || done) { return; } others[i].subscribe(observers[i]); @@ -140,7 +140,7 @@ void subscribe(ObservableSource<?>[] others, int n) { @Override public void onSubscribe(Disposable d) { - DisposableHelper.setOnce(this.d, d); + DisposableHelper.setOnce(this.upstream, d); } @Override @@ -173,7 +173,7 @@ public void onNext(T t) { return; } - HalfSerializer.onNext(actual, v, this, error); + HalfSerializer.onNext(downstream, v, this, error); } @Override @@ -184,7 +184,7 @@ public void onError(Throwable t) { } done = true; cancelAllBut(-1); - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } @Override @@ -192,20 +192,20 @@ public void onComplete() { if (!done) { done = true; cancelAllBut(-1); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } } @Override public boolean isDisposed() { - return DisposableHelper.isDisposed(d.get()); + return DisposableHelper.isDisposed(upstream.get()); } @Override public void dispose() { - DisposableHelper.dispose(d); - for (WithLatestInnerObserver s : observers) { - s.dispose(); + DisposableHelper.dispose(upstream); + for (WithLatestInnerObserver observer : observers) { + observer.dispose(); } } @@ -215,16 +215,16 @@ void innerNext(int index, Object o) { void innerError(int index, Throwable t) { done = true; - DisposableHelper.dispose(d); + DisposableHelper.dispose(upstream); cancelAllBut(index); - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } void innerComplete(int index, boolean nonEmpty) { if (!nonEmpty) { done = true; cancelAllBut(index); - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java index 2227849571..7f00383834 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java @@ -46,11 +46,11 @@ public ObservableZip(ObservableSource<? extends T>[] sources, @Override @SuppressWarnings("unchecked") - public void subscribeActual(Observer<? super R> s) { + public void subscribeActual(Observer<? super R> observer) { ObservableSource<? extends T>[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; for (ObservableSource<? extends T> p : sourcesIterable) { if (count == sources.length) { ObservableSource<? extends T>[] b = new ObservableSource[count + (count >> 2)]; @@ -64,18 +64,18 @@ public void subscribeActual(Observer<? super R> s) { } if (count == 0) { - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); return; } - ZipCoordinator<T, R> zc = new ZipCoordinator<T, R>(s, zipper, count, delayError); + ZipCoordinator<T, R> zc = new ZipCoordinator<T, R>(observer, zipper, count, delayError); zc.subscribe(sources, bufferSize); } static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposable { private static final long serialVersionUID = 2983708048395377667L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super Object[], ? extends R> zipper; final ZipObserver<T, R>[] observers; final T[] row; @@ -87,7 +87,7 @@ static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposa ZipCoordinator(Observer<? super R> actual, Function<? super Object[], ? extends R> zipper, int count, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.zipper = zipper; this.observers = new ZipObserver[count]; this.row = (T[])new Object[count]; @@ -102,7 +102,7 @@ public void subscribe(ObservableSource<? extends T>[] sources, int bufferSize) { } // this makes sure the contents of the observers array is visible this.lazySet(0); - actual.onSubscribe(this); + downstream.onSubscribe(this); for (int i = 0; i < len; i++) { if (cancelled) { return; @@ -152,7 +152,7 @@ public void drain() { int missing = 1; final ZipObserver<T, R>[] zs = observers; - final Observer<? super R> a = actual; + final Observer<? super R> a = downstream; final T[] os = row; final boolean delayError = this.delayError; @@ -179,6 +179,7 @@ public void drain() { if (z.done && !delayError) { Throwable ex = z.error; if (ex != null) { + cancelled = true; cancel(); a.onError(ex); return; @@ -224,6 +225,7 @@ boolean checkTerminated(boolean d, boolean empty, Observer<? super R> a, boolean if (delayError) { if (empty) { Throwable e = source.error; + cancelled = true; cancel(); if (e != null) { a.onError(e); @@ -235,11 +237,13 @@ boolean checkTerminated(boolean d, boolean empty, Observer<? super R> a, boolean } else { Throwable e = source.error; if (e != null) { + cancelled = true; cancel(); a.onError(e); return true; } else if (empty) { + cancelled = true; cancel(); a.onComplete(); return true; @@ -259,15 +263,16 @@ static final class ZipObserver<T, R> implements Observer<T> { volatile boolean done; Throwable error; - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); ZipObserver(ZipCoordinator<T, R> parent, int bufferSize) { this.parent = parent; this.queue = new SpscLinkedArrayQueue<T>(bufferSize); } + @Override - public void onSubscribe(Disposable s) { - DisposableHelper.setOnce(this.s, s); + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); } @Override @@ -290,7 +295,7 @@ public void onComplete() { } public void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); } } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java index 4e01080759..22b7b95196 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java @@ -67,41 +67,39 @@ public void subscribeActual(Observer<? super V> t) { } static final class ZipIterableObserver<T, U, V> implements Observer<T>, Disposable { - final Observer<? super V> actual; + final Observer<? super V> downstream; final Iterator<U> iterator; final BiFunction<? super T, ? super U, ? extends V> zipper; - Disposable s; + Disposable upstream; boolean done; ZipIterableObserver(Observer<? super V> actual, Iterator<U> iterator, BiFunction<? super T, ? super U, ? extends V> zipper) { - this.actual = actual; + this.downstream = actual; this.iterator = iterator; this.zipper = zipper; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(T t) { if (done) { @@ -127,7 +125,7 @@ public void onNext(T t) { return; } - actual.onNext(v); + downstream.onNext(v); boolean b; @@ -141,15 +139,15 @@ public void onNext(T t) { if (!b) { done = true; - s.dispose(); - actual.onComplete(); + upstream.dispose(); + downstream.onComplete(); } } void error(Throwable e) { done = true; - s.dispose(); - actual.onError(e); + upstream.dispose(); + downstream.onError(e); } @Override @@ -159,7 +157,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -168,7 +166,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java b/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java index 6da2c5d377..80034928be 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java @@ -23,48 +23,48 @@ public final class ObserverResourceWrapper<T> extends AtomicReference<Disposable private static final long serialVersionUID = -8612022020200669122L; - final Observer<? super T> actual; + final Observer<? super T> downstream; - final AtomicReference<Disposable> subscription = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); - public ObserverResourceWrapper(Observer<? super T> actual) { - this.actual = actual; + public ObserverResourceWrapper(Observer<? super T> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(Disposable s) { - if (DisposableHelper.setOnce(subscription, s)) { - actual.onSubscribe(this); + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(upstream, d)) { + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { dispose(); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { dispose(); - actual.onComplete(); + downstream.onComplete(); } @Override public void dispose() { - DisposableHelper.dispose(subscription); + DisposableHelper.dispose(upstream); DisposableHelper.dispose(this); } @Override public boolean isDisposed() { - return subscription.get() == DisposableHelper.DISPOSED; + return upstream.get() == DisposableHelper.DISPOSED; } public void setResource(Disposable resource) { diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java index 47c55f5d08..b1052b2fd6 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java @@ -87,7 +87,6 @@ public int parallelism() { static final class ParallelCollectSubscriber<T, C> extends DeferredScalarSubscriber<T, C> { - private static final long serialVersionUID = -4767392946044436228L; final BiConsumer<? super C, ? super T> collector; @@ -105,10 +104,10 @@ static final class ParallelCollectSubscriber<T, C> extends DeferredScalarSubscri @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -137,7 +136,7 @@ public void onError(Throwable t) { } done = true; collection = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -154,7 +153,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java index 4a599b2435..23fae57a80 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java @@ -26,9 +26,9 @@ /** * Calls a Consumer for each upstream value passing by * and handles any failure with a handler function. - * + * <p>History: 2.0.8 - experimental * @param <T> the input value type - * @since 2.0.8 - experimental + * @since 2.2 */ public final class ParallelDoOnNextTry<T> extends ParallelFlowable<T> { @@ -74,46 +74,46 @@ public int parallelism() { static final class ParallelDoOnNextSubscriber<T> implements ConditionalSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final Consumer<? super T> onNext; final BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler; - Subscription s; + Subscription upstream; boolean done; ParallelDoOnNextSubscriber(Subscriber<? super T> actual, Consumer<? super T> onNext, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { - this.actual = actual; + this.downstream = actual; this.onNext = onNext; this.errorHandler = errorHandler; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!tryOnNext(t)) { - s.request(1); + upstream.request(1); } } @@ -157,7 +157,7 @@ public boolean tryOnNext(T t) { } } - actual.onNext(t); + downstream.onNext(t); return true; } } @@ -169,7 +169,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -178,52 +178,53 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } static final class ParallelDoOnNextConditionalSubscriber<T> implements ConditionalSubscriber<T>, Subscription { - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; final Consumer<? super T> onNext; final BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler; - Subscription s; + + Subscription upstream; boolean done; ParallelDoOnNextConditionalSubscriber(ConditionalSubscriber<? super T> actual, Consumer<? super T> onNext, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { - this.actual = actual; + this.downstream = actual; this.onNext = onNext; this.errorHandler = errorHandler; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!tryOnNext(t) && !done) { - s.request(1); + upstream.request(1); } } @@ -267,7 +268,7 @@ public boolean tryOnNext(T t) { } } - return actual.tryOnNext(t); + return downstream.tryOnNext(t); } } @@ -278,7 +279,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -287,7 +288,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java index 5dab623306..1a775d54ae 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java @@ -68,7 +68,7 @@ public int parallelism() { abstract static class BaseFilterSubscriber<T> implements ConditionalSubscriber<T>, Subscription { final Predicate<? super T> predicate; - Subscription s; + Subscription upstream; boolean done; @@ -78,37 +78,37 @@ abstract static class BaseFilterSubscriber<T> implements ConditionalSubscriber<T @Override public final void request(long n) { - s.request(n); + upstream.request(n); } @Override public final void cancel() { - s.cancel(); + upstream.cancel(); } @Override public final void onNext(T t) { if (!tryOnNext(t) && !done) { - s.request(1); + upstream.request(1); } } } static final class ParallelFilterSubscriber<T> extends BaseFilterSubscriber<T> { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; ParallelFilterSubscriber(Subscriber<? super T> actual, Predicate<? super T> predicate) { super(predicate); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -127,7 +127,7 @@ public boolean tryOnNext(T t) { } if (b) { - actual.onNext(t); + downstream.onNext(t); return true; } } @@ -141,33 +141,33 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } } static final class ParallelFilterConditionalSubscriber<T> extends BaseFilterSubscriber<T> { - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; ParallelFilterConditionalSubscriber(ConditionalSubscriber<? super T> actual, Predicate<? super T> predicate) { super(predicate); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -186,7 +186,7 @@ public boolean tryOnNext(T t) { } if (b) { - return actual.tryOnNext(t); + return downstream.tryOnNext(t); } } return false; @@ -199,14 +199,14 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } }} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java index e6aa32f867..bd0923fb70 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java @@ -75,7 +75,7 @@ abstract static class BaseFilterSubscriber<T> implements ConditionalSubscriber<T final BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler; - Subscription s; + Subscription upstream; boolean done; @@ -86,37 +86,37 @@ abstract static class BaseFilterSubscriber<T> implements ConditionalSubscriber<T @Override public final void request(long n) { - s.request(n); + upstream.request(n); } @Override public final void cancel() { - s.cancel(); + upstream.cancel(); } @Override public final void onNext(T t) { if (!tryOnNext(t) && !done) { - s.request(1); + upstream.request(1); } } } static final class ParallelFilterSubscriber<T> extends BaseFilterSubscriber<T> { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; ParallelFilterSubscriber(Subscriber<? super T> actual, Predicate<? super T> predicate, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { super(predicate, errorHandler); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -161,7 +161,7 @@ public boolean tryOnNext(T t) { } if (b) { - actual.onNext(t); + downstream.onNext(t); return true; } return false; @@ -177,35 +177,35 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } } static final class ParallelFilterConditionalSubscriber<T> extends BaseFilterSubscriber<T> { - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; ParallelFilterConditionalSubscriber(ConditionalSubscriber<? super T> actual, Predicate<? super T> predicate, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { super(predicate, errorHandler); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -249,7 +249,7 @@ public boolean tryOnNext(T t) { } } - return b && actual.tryOnNext(t); + return b && downstream.tryOnNext(t); } } return false; @@ -262,14 +262,14 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { if (!done) { done = true; - actual.onComplete(); + downstream.onComplete(); } } }} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java index e50246a988..33d83ca34c 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java @@ -62,7 +62,6 @@ static final class ParallelDispatcher<T> extends AtomicInteger implements FlowableSubscriber<T> { - private static final long serialVersionUID = -4470634016609963609L; final Subscriber<? super T>[] subscribers; @@ -75,7 +74,7 @@ static final class ParallelDispatcher<T> final int limit; - Subscription s; + Subscription upstream; SimpleQueue<T> queue; @@ -109,14 +108,14 @@ static final class ParallelDispatcher<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription<T> qs = (QueueSubscription<T>) s; - int m = qs.requestFusion(QueueSubscription.ANY); + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); if (m == QueueSubscription.SYNC) { sourceMode = m; @@ -204,7 +203,7 @@ public void cancel() { public void onNext(T t) { if (sourceMode == QueueSubscription.NONE) { if (!queue.offer(t)) { - s.cancel(); + upstream.cancel(); onError(new MissingBackpressureException("Queue is full?")); return; } @@ -228,7 +227,7 @@ public void onComplete() { void cancel(int m) { if (requests.decrementAndGet(m) == 0L) { cancelled = true; - this.s.cancel(); + this.upstream.cancel(); if (getAndIncrement() == 0) { queue.clear(); @@ -292,7 +291,7 @@ void drainAsync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); for (Subscriber<? super T> s : a) { s.onError(ex); } @@ -310,7 +309,7 @@ void drainAsync() { int c = ++consumed; if (c == limit) { consumed = 0; - s.request(c); + upstream.request(c); } notReady = 0; } else { @@ -380,7 +379,7 @@ void drainSync() { v = q.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.cancel(); + upstream.cancel(); for (Subscriber<? super T> s : a) { s.onError(ex); } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java index 04ab4033cc..975c151729 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java @@ -63,7 +63,7 @@ abstract static class JoinSubscriptionBase<T> extends AtomicInteger private static final long serialVersionUID = 3100232009247827843L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final JoinInnerSubscriber<T>[] subscribers; @@ -76,7 +76,7 @@ abstract static class JoinSubscriptionBase<T> extends AtomicInteger final AtomicInteger done = new AtomicInteger(); JoinSubscriptionBase(Subscriber<? super T> actual, int n, int prefetch) { - this.actual = actual; + this.downstream = actual; @SuppressWarnings("unchecked") JoinInnerSubscriber<T>[] a = new JoinInnerSubscriber[n]; @@ -110,15 +110,13 @@ public void cancel() { } void cancelAll() { - for (int i = 0; i < subscribers.length; i++) { - JoinInnerSubscriber<T> s = subscribers[i]; + for (JoinInnerSubscriber<T> s : subscribers) { s.cancel(); } } void cleanup() { - for (int i = 0; i < subscribers.length; i++) { - JoinInnerSubscriber<T> s = subscribers[i]; + for (JoinInnerSubscriber<T> s : subscribers) { s.queue = null; } } @@ -144,7 +142,7 @@ static final class JoinSubscription<T> extends JoinSubscriptionBase<T> { public void onNext(JoinInnerSubscriber<T> inner, T value) { if (get() == 0 && compareAndSet(0, 1)) { if (requested.get() != 0) { - actual.onNext(value); + downstream.onNext(value); if (requested.get() != Long.MAX_VALUE) { requested.decrementAndGet(); } @@ -156,7 +154,7 @@ public void onNext(JoinInnerSubscriber<T> inner, T value) { cancelAll(); Throwable mbe = new MissingBackpressureException("Queue full?!"); if (errors.compareAndSet(null, mbe)) { - actual.onError(mbe); + downstream.onError(mbe); } else { RxJavaPlugins.onError(mbe); } @@ -215,7 +213,7 @@ void drainLoop() { JoinInnerSubscriber<T>[] s = this.subscribers; int n = s.length; - Subscriber<? super T> a = this.actual; + Subscriber<? super T> a = this.downstream; for (;;) { @@ -329,7 +327,7 @@ static final class JoinSubscriptionDelayError<T> extends JoinSubscriptionBase<T> void onNext(JoinInnerSubscriber<T> inner, T value) { if (get() == 0 && compareAndSet(0, 1)) { if (requested.get() != 0) { - actual.onNext(value); + downstream.onNext(value); if (requested.get() != Long.MAX_VALUE) { requested.decrementAndGet(); } @@ -393,7 +391,7 @@ void drainLoop() { JoinInnerSubscriber<T>[] s = this.subscribers; int n = s.length; - Subscriber<? super T> a = this.actual; + Subscriber<? super T> a = this.downstream; for (;;) { @@ -516,9 +514,7 @@ static final class JoinInnerSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(prefetch); - } + SubscriptionHelper.setOnce(this, s, prefetch); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java index 6786621f5f..18a627d6ad 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java @@ -70,35 +70,35 @@ public int parallelism() { static final class ParallelMapSubscriber<T, R> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends R> mapper; - Subscription s; + Subscription upstream; boolean done; ParallelMapSubscriber(Subscriber<? super R> actual, Function<? super T, ? extends R> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -118,7 +118,7 @@ public void onNext(T t) { return; } - actual.onNext(v); + downstream.onNext(v); } @Override @@ -128,7 +128,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -137,41 +137,41 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } static final class ParallelMapConditionalSubscriber<T, R> implements ConditionalSubscriber<T>, Subscription { - final ConditionalSubscriber<? super R> actual; + final ConditionalSubscriber<? super R> downstream; final Function<? super T, ? extends R> mapper; - Subscription s; + Subscription upstream; boolean done; ParallelMapConditionalSubscriber(ConditionalSubscriber<? super R> actual, Function<? super T, ? extends R> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -191,7 +191,7 @@ public void onNext(T t) { return; } - actual.onNext(v); + downstream.onNext(v); } @Override @@ -210,7 +210,7 @@ public boolean tryOnNext(T t) { return false; } - return actual.tryOnNext(v); + return downstream.tryOnNext(v); } @Override @@ -220,7 +220,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -229,7 +229,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java index 59c8b85fde..73c19733bd 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java @@ -26,10 +26,10 @@ /** * Maps each 'rail' of the source ParallelFlowable with a mapper function * and handle any failure based on a handler function. - * + * <p>History: 2.0.8 - experimental * @param <T> the input value type * @param <R> the output value type - * @since 2.0.8 - experimental + * @since 2.2 */ public final class ParallelMapTry<T, R> extends ParallelFlowable<R> { @@ -75,46 +75,46 @@ public int parallelism() { static final class ParallelMapTrySubscriber<T, R> implements ConditionalSubscriber<T>, Subscription { - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends R> mapper; final BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler; - Subscription s; + Subscription upstream; boolean done; ParallelMapTrySubscriber(Subscriber<? super R> actual, Function<? super T, ? extends R> mapper, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.errorHandler = errorHandler; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!tryOnNext(t) && !done) { - s.request(1); + upstream.request(1); } } @@ -160,7 +160,7 @@ public boolean tryOnNext(T t) { } } - actual.onNext(v); + downstream.onNext(v); return true; } } @@ -172,7 +172,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -181,52 +181,53 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } static final class ParallelMapTryConditionalSubscriber<T, R> implements ConditionalSubscriber<T>, Subscription { - final ConditionalSubscriber<? super R> actual; + final ConditionalSubscriber<? super R> downstream; final Function<? super T, ? extends R> mapper; final BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler; - Subscription s; + + Subscription upstream; boolean done; ParallelMapTryConditionalSubscriber(ConditionalSubscriber<? super R> actual, Function<? super T, ? extends R> mapper, BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.errorHandler = errorHandler; } @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onNext(T t) { if (!tryOnNext(t) && !done) { - s.request(1); + upstream.request(1); } } @@ -272,7 +273,7 @@ public boolean tryOnNext(T t) { } } - return actual.tryOnNext(v); + return downstream.tryOnNext(v); } } @@ -283,7 +284,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } @Override @@ -292,7 +293,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java index ebcc3c91a4..3e7914c02a 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java @@ -87,16 +87,16 @@ public int parallelism() { static final class ParallelPeekSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final ParallelPeek<T> parent; - Subscription s; + Subscription upstream; boolean done; ParallelPeekSubscriber(Subscriber<? super T> actual, ParallelPeek<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } @@ -108,7 +108,7 @@ public void request(long n) { Exceptions.throwIfFatal(ex); RxJavaPlugins.onError(ex); } - s.request(n); + upstream.request(n); } @Override @@ -119,25 +119,25 @@ public void cancel() { Exceptions.throwIfFatal(ex); RxJavaPlugins.onError(ex); } - s.cancel(); + upstream.cancel(); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; try { parent.onSubscribe.accept(s); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); s.cancel(); - actual.onSubscribe(EmptySubscription.INSTANCE); + downstream.onSubscribe(EmptySubscription.INSTANCE); onError(ex); return; } - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -152,7 +152,7 @@ public void onNext(T t) { return; } - actual.onNext(t); + downstream.onNext(t); try { parent.onAfterNext.accept(t); @@ -177,7 +177,7 @@ public void onError(Throwable t) { Exceptions.throwIfFatal(ex); t = new CompositeException(t, ex); } - actual.onError(t); + downstream.onError(t); try { parent.onAfterTerminated.run(); @@ -195,10 +195,10 @@ public void onComplete() { parent.onComplete.run(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onComplete(); + downstream.onComplete(); try { parent.onAfterTerminated.run(); diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java index f747e8c83c..0c3bcfbba1 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java @@ -86,7 +86,6 @@ public int parallelism() { static final class ParallelReduceSubscriber<T, R> extends DeferredScalarSubscriber<T, R> { - private static final long serialVersionUID = 8200530050639449080L; final BiFunction<R, ? super T, R> reducer; @@ -103,10 +102,10 @@ static final class ParallelReduceSubscriber<T, R> extends DeferredScalarSubscrib @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -138,7 +137,7 @@ public void onError(Throwable t) { } done = true; accumulator = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -155,7 +154,7 @@ public void onComplete() { @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java index 98d536045c..8757752322 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java @@ -52,7 +52,6 @@ protected void subscribeActual(Subscriber<? super T> s) { static final class ParallelReduceFullMainSubscriber<T> extends DeferredScalarSubscription<T> { - private static final long serialVersionUID = -5370107872170712765L; final ParallelReduceFullInnerSubscriber<T>[] subscribers; @@ -117,7 +116,7 @@ public void cancel() { void innerError(Throwable ex) { if (error.compareAndSet(null, ex)) { cancel(); - actual.onError(ex); + downstream.onError(ex); } else { if (ex != error.get()) { RxJavaPlugins.onError(ex); @@ -153,7 +152,7 @@ void innerComplete(T value) { if (sp != null) { complete(sp.first); } else { - actual.onComplete(); + downstream.onComplete(); } } } @@ -180,9 +179,7 @@ static final class ParallelReduceFullInnerSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java index 3281a20b9d..8643cc6d2b 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java @@ -22,6 +22,8 @@ import io.reactivex.exceptions.MissingBackpressureException; import io.reactivex.internal.fuseable.ConditionalSubscriber; import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.schedulers.SchedulerMultiWorkerSupport; +import io.reactivex.internal.schedulers.SchedulerMultiWorkerSupport.WorkerCallback; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.internal.util.BackpressureHelper; import io.reactivex.parallel.ParallelFlowable; @@ -47,7 +49,7 @@ public ParallelRunOn(ParallelFlowable<? extends T> parent, } @Override - public void subscribe(Subscriber<? super T>[] subscribers) { + public void subscribe(final Subscriber<? super T>[] subscribers) { if (!validate(subscribers)) { return; } @@ -55,26 +57,50 @@ public void subscribe(Subscriber<? super T>[] subscribers) { int n = subscribers.length; @SuppressWarnings("unchecked") - Subscriber<T>[] parents = new Subscriber[n]; + final Subscriber<T>[] parents = new Subscriber[n]; + + if (scheduler instanceof SchedulerMultiWorkerSupport) { + SchedulerMultiWorkerSupport multiworker = (SchedulerMultiWorkerSupport) scheduler; + multiworker.createWorkers(n, new MultiWorkerCallback(subscribers, parents)); + } else { + for (int i = 0; i < n; i++) { + createSubscriber(i, subscribers, parents, scheduler.createWorker()); + } + } + source.subscribe(parents); + } - int prefetch = this.prefetch; + void createSubscriber(int i, Subscriber<? super T>[] subscribers, + Subscriber<T>[] parents, Scheduler.Worker worker) { - for (int i = 0; i < n; i++) { - Subscriber<? super T> a = subscribers[i]; + Subscriber<? super T> a = subscribers[i]; - Worker w = scheduler.createWorker(); - SpscArrayQueue<T> q = new SpscArrayQueue<T>(prefetch); + SpscArrayQueue<T> q = new SpscArrayQueue<T>(prefetch); - if (a instanceof ConditionalSubscriber) { - parents[i] = new RunOnConditionalSubscriber<T>((ConditionalSubscriber<? super T>)a, prefetch, q, w); - } else { - parents[i] = new RunOnSubscriber<T>(a, prefetch, q, w); - } + if (a instanceof ConditionalSubscriber) { + parents[i] = new RunOnConditionalSubscriber<T>((ConditionalSubscriber<? super T>)a, prefetch, q, worker); + } else { + parents[i] = new RunOnSubscriber<T>(a, prefetch, q, worker); } - - source.subscribe(parents); } + final class MultiWorkerCallback implements WorkerCallback { + + final Subscriber<? super T>[] subscribers; + + final Subscriber<T>[] parents; + + MultiWorkerCallback(Subscriber<? super T>[] subscribers, + Subscriber<T>[] parents) { + this.subscribers = subscribers; + this.parents = parents; + } + + @Override + public void onWorker(int i, Worker w) { + createSubscriber(i, subscribers, parents, w); + } + } @Override public int parallelism() { @@ -94,7 +120,7 @@ abstract static class BaseRunOnSubscriber<T> extends AtomicInteger final Worker worker; - Subscription s; + Subscription upstream; volatile boolean done; @@ -119,7 +145,7 @@ public final void onNext(T t) { return; } if (!queue.offer(t)) { - s.cancel(); + upstream.cancel(); onError(new MissingBackpressureException("Queue is full?!")); return; } @@ -158,7 +184,7 @@ public final void request(long n) { public final void cancel() { if (!cancelled) { cancelled = true; - s.cancel(); + upstream.cancel(); worker.dispose(); if (getAndIncrement() == 0) { @@ -178,19 +204,19 @@ static final class RunOnSubscriber<T> extends BaseRunOnSubscriber<T> { private static final long serialVersionUID = 1075119423897941642L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; RunOnSubscriber(Subscriber<? super T> actual, int prefetch, SpscArrayQueue<T> queue, Worker worker) { super(prefetch, queue, worker); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); } @@ -201,7 +227,7 @@ public void run() { int missed = 1; int c = consumed; SpscArrayQueue<T> q = queue; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; int lim = limit; for (;;) { @@ -251,7 +277,7 @@ public void run() { int p = ++c; if (p == lim) { c = 0; - s.request(p); + upstream.request(p); } } @@ -302,19 +328,19 @@ static final class RunOnConditionalSubscriber<T> extends BaseRunOnSubscriber<T> private static final long serialVersionUID = 1075119423897941642L; - final ConditionalSubscriber<? super T> actual; + final ConditionalSubscriber<? super T> downstream; RunOnConditionalSubscriber(ConditionalSubscriber<? super T> actual, int prefetch, SpscArrayQueue<T> queue, Worker worker) { super(prefetch, queue, worker); - this.actual = actual; + this.downstream = actual; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(prefetch); } @@ -325,7 +351,7 @@ public void run() { int missed = 1; int c = consumed; SpscArrayQueue<T> q = queue; - ConditionalSubscriber<? super T> a = actual; + ConditionalSubscriber<? super T> a = downstream; int lim = limit; for (;;) { @@ -375,7 +401,7 @@ public void run() { int p = ++c; if (p == lim) { c = 0; - s.request(p); + upstream.request(p); } } diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java index 33abbd132f..a7c4947709 100644 --- a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java @@ -58,7 +58,7 @@ static final class SortedJoinSubscription<T> private static final long serialVersionUID = 3481980673745556697L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final SortedJoinInnerSubscriber<T>[] subscribers; @@ -78,7 +78,7 @@ static final class SortedJoinSubscription<T> @SuppressWarnings("unchecked") SortedJoinSubscription(Subscriber<? super T> actual, int n, Comparator<? super T> comparator) { - this.actual = actual; + this.downstream = actual; this.comparator = comparator; SortedJoinInnerSubscriber<T>[] s = new SortedJoinInnerSubscriber[n]; @@ -142,7 +142,7 @@ void drain() { } int missed = 1; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; List<T>[] lists = this.lists; int[] indexes = this.indexes; int n = indexes.length; @@ -266,7 +266,6 @@ static final class SortedJoinInnerSubscriber<T> extends AtomicReference<Subscription> implements FlowableSubscriber<List<T>> { - private static final long serialVersionUID = 6751017204873808094L; final SortedJoinSubscription<T> parent; @@ -280,9 +279,7 @@ static final class SortedJoinInnerSubscriber<T> @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java index d0acba1234..2584506b59 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java @@ -32,7 +32,7 @@ public SingleAmb(SingleSource<? extends T>[] sources, Iterable<? extends SingleS @Override @SuppressWarnings("unchecked") - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { SingleSource<? extends T>[] sources = this.sources; int count = 0; if (sources == null) { @@ -40,7 +40,7 @@ protected void subscribeActual(final SingleObserver<? super T> s) { try { for (SingleSource<? extends T> element : sourcesIterable) { if (element == null) { - EmptyDisposable.error(new NullPointerException("One of the sources is null"), s); + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); return; } if (count == sources.length) { @@ -52,70 +52,76 @@ protected void subscribeActual(final SingleObserver<? super T> s) { } } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } } else { count = sources.length; } + final AtomicBoolean winner = new AtomicBoolean(); final CompositeDisposable set = new CompositeDisposable(); - AmbSingleObserver<T> shared = new AmbSingleObserver<T>(s, set); - s.onSubscribe(set); + observer.onSubscribe(set); for (int i = 0; i < count; i++) { SingleSource<? extends T> s1 = sources[i]; - if (shared.get()) { + if (set.isDisposed()) { return; } if (s1 == null) { set.dispose(); Throwable e = new NullPointerException("One of the sources is null"); - if (shared.compareAndSet(false, true)) { - s.onError(e); + if (winner.compareAndSet(false, true)) { + observer.onError(e); } else { RxJavaPlugins.onError(e); } return; } - s1.subscribe(shared); + s1.subscribe(new AmbSingleObserver<T>(observer, set, winner)); } } - static final class AmbSingleObserver<T> extends AtomicBoolean implements SingleObserver<T> { - - private static final long serialVersionUID = -1944085461036028108L; + static final class AmbSingleObserver<T> implements SingleObserver<T> { final CompositeDisposable set; - final SingleObserver<? super T> s; + final SingleObserver<? super T> downstream; + + final AtomicBoolean winner; + + Disposable upstream; - AmbSingleObserver(SingleObserver<? super T> s, CompositeDisposable set) { - this.s = s; + AmbSingleObserver(SingleObserver<? super T> observer, CompositeDisposable set, AtomicBoolean winner) { + this.downstream = observer; this.set = set; + this.winner = winner; } @Override public void onSubscribe(Disposable d) { + this.upstream = d; set.add(d); } @Override public void onSuccess(T value) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - s.onSuccess(value); + downstream.onSuccess(value); } } @Override public void onError(Throwable e) { - if (compareAndSet(false, true)) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); set.dispose(); - s.onError(e); + downstream.onError(e); } else { RxJavaPlugins.onError(e); } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleCache.java b/src/main/java/io/reactivex/internal/operators/single/SingleCache.java index d658f8cd5a..1752a283b0 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleCache.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleCache.java @@ -43,9 +43,9 @@ public SingleCache(SingleSource<? extends T> source) { } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - CacheDisposable<T> d = new CacheDisposable<T>(s, this); - s.onSubscribe(d); + protected void subscribeActual(final SingleObserver<? super T> observer) { + CacheDisposable<T> d = new CacheDisposable<T>(observer, this); + observer.onSubscribe(d); if (add(d)) { if (d.isDisposed()) { @@ -54,9 +54,9 @@ protected void subscribeActual(final SingleObserver<? super T> s) { } else { Throwable ex = error; if (ex != null) { - s.onError(ex); + observer.onError(ex); } else { - s.onSuccess(value); + observer.onSuccess(value); } return; } @@ -131,7 +131,7 @@ public void onSuccess(T value) { for (CacheDisposable<T> d : observers.getAndSet(TERMINATED)) { if (!d.isDisposed()) { - d.actual.onSuccess(value); + d.downstream.onSuccess(value); } } } @@ -143,7 +143,7 @@ public void onError(Throwable e) { for (CacheDisposable<T> d : observers.getAndSet(TERMINATED)) { if (!d.isDisposed()) { - d.actual.onError(e); + d.downstream.onError(e); } } } @@ -154,12 +154,12 @@ static final class CacheDisposable<T> private static final long serialVersionUID = 7514387411091976596L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SingleCache<T> parent; CacheDisposable(SingleObserver<? super T> actual, SingleCache<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleContains.java b/src/main/java/io/reactivex/internal/operators/single/SingleContains.java index b57d8eefbd..c8fb355f48 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleContains.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleContains.java @@ -33,22 +33,22 @@ public SingleContains(SingleSource<T> source, Object value, BiPredicate<Object, } @Override - protected void subscribeActual(final SingleObserver<? super Boolean> s) { + protected void subscribeActual(final SingleObserver<? super Boolean> observer) { - source.subscribe(new Single(s)); + source.subscribe(new ContainsSingleObserver(observer)); } - final class Single implements SingleObserver<T> { + final class ContainsSingleObserver implements SingleObserver<T> { - private final SingleObserver<? super Boolean> s; + private final SingleObserver<? super Boolean> downstream; - Single(SingleObserver<? super Boolean> s) { - this.s = s; + ContainsSingleObserver(SingleObserver<? super Boolean> observer) { + this.downstream = observer; } @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + downstream.onSubscribe(d); } @Override @@ -59,15 +59,15 @@ public void onSuccess(T v) { b = comparer.test(v, value); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(ex); + downstream.onError(ex); return; } - s.onSuccess(b); + downstream.onSuccess(b); } @Override public void onError(Throwable e) { - s.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java b/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java index 69cac3c393..2b0dcfae85 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java @@ -31,9 +31,9 @@ public SingleCreate(SingleOnSubscribe<T> source) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - Emitter<T> parent = new Emitter<T>(s); - s.onSubscribe(parent); + protected void subscribeActual(SingleObserver<? super T> observer) { + Emitter<T> parent = new Emitter<T>(observer); + observer.onSubscribe(parent); try { source.subscribe(parent); @@ -47,14 +47,13 @@ static final class Emitter<T> extends AtomicReference<Disposable> implements SingleEmitter<T>, Disposable { - final SingleObserver<? super T> actual; - - Emitter(SingleObserver<? super T> actual) { - this.actual = actual; - } + private static final long serialVersionUID = -2467358622224974244L; + final SingleObserver<? super T> downstream; - private static final long serialVersionUID = -2467358622224974244L; + Emitter(SingleObserver<? super T> downstream) { + this.downstream = downstream; + } @Override public void onSuccess(T value) { @@ -63,9 +62,9 @@ public void onSuccess(T value) { if (d != DisposableHelper.DISPOSED) { try { if (value == null) { - actual.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); + downstream.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); } else { - actual.onSuccess(value); + downstream.onSuccess(value); } } finally { if (d != null) { @@ -92,7 +91,7 @@ public boolean tryOnError(Throwable t) { Disposable d = getAndSet(DisposableHelper.DISPOSED); if (d != DisposableHelper.DISPOSED) { try { - actual.onError(t); + downstream.onError(t); } finally { if (d != null) { d.dispose(); @@ -123,5 +122,10 @@ public void dispose() { public boolean isDisposed() { return DisposableHelper.isDisposed(get()); } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java b/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java index e3ffe8c0ce..0f7a66dc38 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java @@ -29,18 +29,18 @@ public SingleDefer(Callable<? extends SingleSource<? extends T>> singleSupplier) } @Override - protected void subscribeActual(SingleObserver<? super T> s) { + protected void subscribeActual(SingleObserver<? super T> observer) { SingleSource<? extends T> next; try { next = ObjectHelper.requireNonNull(singleSupplier.call(), "The singleSupplier returned a null SingleSource"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - EmptyDisposable.error(e, s); + EmptyDisposable.error(e, observer); return; } - next.subscribe(s); + next.subscribe(observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java index 7387fbaacd..3ea0104a50 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java @@ -21,34 +21,35 @@ public final class SingleDelay<T> extends Single<T> { - final SingleSource<? extends T> source; final long time; final TimeUnit unit; final Scheduler scheduler; + final boolean delayError; - public SingleDelay(SingleSource<? extends T> source, long time, TimeUnit unit, Scheduler scheduler) { + public SingleDelay(SingleSource<? extends T> source, long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { this.source = source; this.time = time; this.unit = unit; this.scheduler = scheduler; + this.delayError = delayError; } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { final SequentialDisposable sd = new SequentialDisposable(); - s.onSubscribe(sd); - source.subscribe(new Delay(sd, s)); + observer.onSubscribe(sd); + source.subscribe(new Delay(sd, observer)); } final class Delay implements SingleObserver<T> { private final SequentialDisposable sd; - final SingleObserver<? super T> s; + final SingleObserver<? super T> downstream; - Delay(SequentialDisposable sd, SingleObserver<? super T> s) { + Delay(SequentialDisposable sd, SingleObserver<? super T> observer) { this.sd = sd; - this.s = s; + this.downstream = observer; } @Override @@ -63,7 +64,7 @@ public void onSuccess(final T value) { @Override public void onError(final Throwable e) { - sd.replace(scheduler.scheduleDirect(new OnError(e), 0, unit)); + sd.replace(scheduler.scheduleDirect(new OnError(e), delayError ? time : 0, unit)); } final class OnSuccess implements Runnable { @@ -75,7 +76,7 @@ final class OnSuccess implements Runnable { @Override public void run() { - s.onSuccess(value); + downstream.onSuccess(value); } } @@ -88,7 +89,7 @@ final class OnError implements Runnable { @Override public void run() { - s.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java index 9f4845f51c..86dbd4d39f 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java @@ -32,23 +32,22 @@ public SingleDelayWithCompletable(SingleSource<T> source, CompletableSource othe } @Override - protected void subscribeActual(SingleObserver<? super T> subscriber) { - other.subscribe(new OtherObserver<T>(subscriber, source)); + protected void subscribeActual(SingleObserver<? super T> observer) { + other.subscribe(new OtherObserver<T>(observer, source)); } static final class OtherObserver<T> extends AtomicReference<Disposable> implements CompletableObserver, Disposable { - private static final long serialVersionUID = -8565274649390031272L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SingleSource<T> source; OtherObserver(SingleObserver<? super T> actual, SingleSource<T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; } @@ -56,18 +55,18 @@ static final class OtherObserver<T> public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - source.subscribe(new ResumeSingleObserver<T>(this, actual)); + source.subscribe(new ResumeSingleObserver<T>(this, downstream)); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java index 2e1e8fcbfd..c905bba0c6 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java @@ -33,25 +33,24 @@ public SingleDelayWithObservable(SingleSource<T> source, ObservableSource<U> oth } @Override - protected void subscribeActual(SingleObserver<? super T> subscriber) { - other.subscribe(new OtherSubscriber<T, U>(subscriber, source)); + protected void subscribeActual(SingleObserver<? super T> observer) { + other.subscribe(new OtherSubscriber<T, U>(observer, source)); } static final class OtherSubscriber<T, U> extends AtomicReference<Disposable> implements Observer<U>, Disposable { - private static final long serialVersionUID = -8565274649390031272L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SingleSource<T> source; boolean done; OtherSubscriber(SingleObserver<? super T> actual, SingleSource<T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; } @@ -59,7 +58,7 @@ static final class OtherSubscriber<T, U> public void onSubscribe(Disposable d) { if (DisposableHelper.set(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -76,7 +75,7 @@ public void onError(Throwable e) { return; } done = true; - actual.onError(e); + downstream.onError(e); } @Override @@ -85,7 +84,7 @@ public void onComplete() { return; } done = true; - source.subscribe(new ResumeSingleObserver<T>(this, actual)); + source.subscribe(new ResumeSingleObserver<T>(this, downstream)); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java index 88bd1f8085..ee93007020 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java @@ -36,36 +36,35 @@ public SingleDelayWithPublisher(SingleSource<T> source, Publisher<U> other) { } @Override - protected void subscribeActual(SingleObserver<? super T> subscriber) { - other.subscribe(new OtherSubscriber<T, U>(subscriber, source)); + protected void subscribeActual(SingleObserver<? super T> observer) { + other.subscribe(new OtherSubscriber<T, U>(observer, source)); } static final class OtherSubscriber<T, U> extends AtomicReference<Disposable> implements FlowableSubscriber<U>, Disposable { - private static final long serialVersionUID = -8565274649390031272L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SingleSource<T> source; boolean done; - Subscription s; + Subscription upstream; OtherSubscriber(SingleObserver<? super T> actual, SingleSource<T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -73,7 +72,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(U value) { - s.cancel(); + upstream.cancel(); onComplete(); } @@ -84,7 +83,7 @@ public void onError(Throwable e) { return; } done = true; - actual.onError(e); + downstream.onError(e); } @Override @@ -93,12 +92,12 @@ public void onComplete() { return; } done = true; - source.subscribe(new ResumeSingleObserver<T>(this, actual)); + source.subscribe(new ResumeSingleObserver<T>(this, downstream)); } @Override public void dispose() { - s.cancel(); + upstream.cancel(); DisposableHelper.dispose(this); } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java index 32e2462513..83255b95c3 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java @@ -32,42 +32,41 @@ public SingleDelayWithSingle(SingleSource<T> source, SingleSource<U> other) { } @Override - protected void subscribeActual(SingleObserver<? super T> subscriber) { - other.subscribe(new OtherObserver<T, U>(subscriber, source)); + protected void subscribeActual(SingleObserver<? super T> observer) { + other.subscribe(new OtherObserver<T, U>(observer, source)); } static final class OtherObserver<T, U> extends AtomicReference<Disposable> implements SingleObserver<U>, Disposable { - private static final long serialVersionUID = -8565274649390031272L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SingleSource<T> source; OtherObserver(SingleObserver<? super T> actual, SingleSource<T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.set(this, d)) { + if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(U value) { - source.subscribe(new ResumeSingleObserver<T>(this, actual)); + source.subscribe(new ResumeSingleObserver<T>(this, downstream)); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java new file mode 100644 index 0000000000..2e402b05da --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source to a Notification, then + * maps it back to the corresponding signal type. + * @param <T> the element type of the source + * @param <R> the element type of the Notification and result + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleDematerialize<T, R> extends Maybe<R> { + + final Single<T> source; + + final Function<? super T, Notification<R>> selector; + + public SingleDematerialize(Single<T> source, Function<? super T, Notification<R>> selector) { + this.source = source; + this.selector = selector; + } + + @Override + protected void subscribeActual(MaybeObserver<? super R> observer) { + source.subscribe(new DematerializeObserver<T, R>(observer, selector)); + } + + static final class DematerializeObserver<T, R> implements SingleObserver<T>, Disposable { + + final MaybeObserver<? super R> downstream; + + final Function<? super T, Notification<R>> selector; + + Disposable upstream; + + DematerializeObserver(MaybeObserver<? super R> downstream, + Function<? super T, Notification<R>> selector) { + this.downstream = downstream; + this.selector = selector; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + Notification<R> notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + if (notification.isOnNext()) { + downstream.onSuccess(notification.getValue()); + } else if (notification.isOnComplete()) { + downstream.onComplete(); + } else { + downstream.onError(notification.getError()); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java b/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java new file mode 100644 index 0000000000..ad9883a1a2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Breaks the references between the upstream and downstream when the Maybe terminates. + * <p>History: 2.1.5 - experimental + * @param <T> the value type + * @since 2.2 + */ +public final class SingleDetach<T> extends Single<T> { + + final SingleSource<T> source; + + public SingleDetach(SingleSource<T> source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new DetachSingleObserver<T>(observer)); + } + + static final class DetachSingleObserver<T> implements SingleObserver<T>, Disposable { + + SingleObserver<? super T> downstream; + + Disposable upstream; + + DetachSingleObserver(SingleObserver<? super T> downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + SingleObserver<? super T> a = downstream; + if (a != null) { + downstream = null; + a.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + SingleObserver<? super T> a = downstream; + if (a != null) { + downstream = null; + a.onError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java index 348a0edf9d..208bb0ecba 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.single; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Consumer; @@ -23,10 +22,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class SingleDoAfterSuccess<T> extends Single<T> { final SingleSource<T> source; @@ -39,35 +38,35 @@ public SingleDoAfterSuccess(SingleSource<T> source, Consumer<? super T> onAfterS } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - source.subscribe(new DoAfterObserver<T>(s, onAfterSuccess)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new DoAfterObserver<T>(observer, onAfterSuccess)); } static final class DoAfterObserver<T> implements SingleObserver<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Consumer<? super T> onAfterSuccess; - Disposable d; + Disposable upstream; DoAfterObserver(SingleObserver<? super T> actual, Consumer<? super T> onAfterSuccess) { - this.actual = actual; + this.downstream = actual; this.onAfterSuccess = onAfterSuccess; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T t) { - actual.onSuccess(t); + downstream.onSuccess(t); try { onAfterSuccess.accept(t); @@ -80,17 +79,17 @@ public void onSuccess(T t) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java index b4b7d58f89..eed7993231 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java @@ -24,8 +24,9 @@ /** * Calls an action after pushing the current item or an error to the downstream. + * <p>History: 2.0.6 - experimental * @param <T> the value type - * @since 2.0.6 - experimental + * @since 2.1 */ public final class SingleDoAfterTerminate<T> extends Single<T> { @@ -39,54 +40,54 @@ public SingleDoAfterTerminate(SingleSource<T> source, Action onAfterTerminate) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - source.subscribe(new DoAfterTerminateObserver<T>(s, onAfterTerminate)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new DoAfterTerminateObserver<T>(observer, onAfterTerminate)); } static final class DoAfterTerminateObserver<T> implements SingleObserver<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Action onAfterTerminate; - Disposable d; + Disposable upstream; DoAfterTerminateObserver(SingleObserver<? super T> actual, Action onAfterTerminate) { - this.actual = actual; + this.downstream = actual; this.onAfterTerminate = onAfterTerminate; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T t) { - actual.onSuccess(t); + downstream.onSuccess(t); onAfterTerminate(); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); onAfterTerminate(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } private void onAfterTerminate() { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java index fddf36d050..7a6bc67258 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.*; -import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Action; @@ -25,11 +24,10 @@ /** * Execute an action after an onSuccess, onError or a dispose event. - * + * <p>History: 2.0.1 - experimental * @param <T> the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class SingleDoFinally<T> extends Single<T> { final SingleSource<T> source; @@ -42,55 +40,55 @@ public SingleDoFinally(SingleSource<T> source, Action onFinally) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - source.subscribe(new DoFinallyObserver<T>(s, onFinally)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new DoFinallyObserver<T>(observer, onFinally)); } static final class DoFinallyObserver<T> extends AtomicInteger implements SingleObserver<T>, Disposable { private static final long serialVersionUID = 4109457741734051389L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Action onFinally; - Disposable d; + Disposable upstream; DoFinallyObserver(SingleObserver<? super T> actual, Action onFinally) { - this.actual = actual; + this.downstream = actual; this.onFinally = onFinally; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T t) { - actual.onSuccess(t); + downstream.onSuccess(t); runFinally(); } @Override public void onError(Throwable t) { - actual.onError(t); + downstream.onError(t); runFinally(); } @Override public void dispose() { - d.dispose(); + upstream.dispose(); runFinally(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } void runFinally() { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java index 0f31f2abc2..29cd86d6b7 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java @@ -33,9 +33,9 @@ public SingleDoOnDispose(SingleSource<T> source, Action onDispose) { } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - source.subscribe(new DoOnDisposeObserver<T>(s, onDispose)); + source.subscribe(new DoOnDisposeObserver<T>(observer, onDispose)); } static final class DoOnDisposeObserver<T> @@ -43,12 +43,12 @@ static final class DoOnDisposeObserver<T> implements SingleObserver<T>, Disposable { private static final long serialVersionUID = -8583764624474935784L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; - Disposable d; + Disposable upstream; DoOnDisposeObserver(SingleObserver<? super T> actual, Action onDispose) { - this.actual = actual; + this.downstream = actual; this.lazySet(onDispose); } @@ -62,31 +62,31 @@ public void dispose() { Exceptions.throwIfFatal(ex); RxJavaPlugins.onError(ex); } - d.dispose(); + upstream.dispose(); } } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java index bd7bcd0d68..c20eb7fc41 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java @@ -30,26 +30,26 @@ public SingleDoOnError(SingleSource<T> source, Consumer<? super Throwable> onErr } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - source.subscribe(new DoOnError(s)); + source.subscribe(new DoOnError(observer)); } final class DoOnError implements SingleObserver<T> { - private final SingleObserver<? super T> s; + private final SingleObserver<? super T> downstream; - DoOnError(SingleObserver<? super T> s) { - this.s = s; + DoOnError(SingleObserver<? super T> observer) { + this.downstream = observer; } @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + downstream.onSubscribe(d); } @Override public void onSuccess(T value) { - s.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -60,7 +60,7 @@ public void onError(Throwable e) { Exceptions.throwIfFatal(ex); e = new CompositeException(e, ex); } - s.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java index c262b51ff6..e057642f18 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java @@ -32,21 +32,21 @@ public SingleDoOnEvent(SingleSource<T> source, BiConsumer<? super T, ? super Thr } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - source.subscribe(new DoOnEvent(s)); + source.subscribe(new DoOnEvent(observer)); } final class DoOnEvent implements SingleObserver<T> { - private final SingleObserver<? super T> s; + private final SingleObserver<? super T> downstream; - DoOnEvent(SingleObserver<? super T> s) { - this.s = s; + DoOnEvent(SingleObserver<? super T> observer) { + this.downstream = observer; } @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + downstream.onSubscribe(d); } @Override @@ -55,11 +55,11 @@ public void onSuccess(T value) { onEvent.accept(value, null); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(ex); + downstream.onError(ex); return; } - s.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -70,7 +70,7 @@ public void onError(Throwable e) { Exceptions.throwIfFatal(ex); e = new CompositeException(e, ex); } - s.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java index e421a05492..4103ad6c79 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java @@ -37,20 +37,20 @@ public SingleDoOnSubscribe(SingleSource<T> source, Consumer<? super Disposable> } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - source.subscribe(new DoOnSubscribeSingleObserver<T>(s, onSubscribe)); + protected void subscribeActual(final SingleObserver<? super T> observer) { + source.subscribe(new DoOnSubscribeSingleObserver<T>(observer, onSubscribe)); } static final class DoOnSubscribeSingleObserver<T> implements SingleObserver<T> { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Consumer<? super Disposable> onSubscribe; boolean done; DoOnSubscribeSingleObserver(SingleObserver<? super T> actual, Consumer<? super Disposable> onSubscribe) { - this.actual = actual; + this.downstream = actual; this.onSubscribe = onSubscribe; } @@ -62,11 +62,11 @@ public void onSubscribe(Disposable d) { Exceptions.throwIfFatal(ex); done = true; d.dispose(); - EmptyDisposable.error(ex, actual); + EmptyDisposable.error(ex, downstream); return; } - actual.onSubscribe(d); + downstream.onSubscribe(d); } @Override @@ -74,7 +74,7 @@ public void onSuccess(T value) { if (done) { return; } - actual.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -83,7 +83,7 @@ public void onError(Throwable e) { RxJavaPlugins.onError(e); return; } - actual.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java index 34ee281777..f915c984cd 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java @@ -30,21 +30,22 @@ public SingleDoOnSuccess(SingleSource<T> source, Consumer<? super T> onSuccess) } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - source.subscribe(new DoOnSuccess(s)); + source.subscribe(new DoOnSuccess(observer)); } final class DoOnSuccess implements SingleObserver<T> { - private final SingleObserver<? super T> s; - DoOnSuccess(SingleObserver<? super T> s) { - this.s = s; + final SingleObserver<? super T> downstream; + + DoOnSuccess(SingleObserver<? super T> observer) { + this.downstream = observer; } @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + downstream.onSubscribe(d); } @Override @@ -53,15 +54,15 @@ public void onSuccess(T value) { onSuccess.accept(value); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - s.onError(ex); + downstream.onError(ex); return; } - s.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - s.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java new file mode 100644 index 0000000000..7497aff902 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class SingleDoOnTerminate<T> extends Single<T> { + + final SingleSource<T> source; + + final Action onTerminate; + + public SingleDoOnTerminate(SingleSource<T> source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(final SingleObserver<? super T> observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements SingleObserver<T> { + + final SingleObserver<? super T> downstream; + + DoOnTerminate(SingleObserver<? super T> observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java index 0ed22f4c1b..07d3f36602 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java @@ -31,32 +31,33 @@ public SingleEquals(SingleSource<? extends T> first, SingleSource<? extends T> s } @Override - protected void subscribeActual(final SingleObserver<? super Boolean> s) { + protected void subscribeActual(final SingleObserver<? super Boolean> observer) { final AtomicInteger count = new AtomicInteger(); final Object[] values = { null, null }; final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + observer.onSubscribe(set); - first.subscribe(new InnerObserver<T>(0, set, values, s, count)); - second.subscribe(new InnerObserver<T>(1, set, values, s, count)); + first.subscribe(new InnerObserver<T>(0, set, values, observer, count)); + second.subscribe(new InnerObserver<T>(1, set, values, observer, count)); } static class InnerObserver<T> implements SingleObserver<T> { final int index; final CompositeDisposable set; final Object[] values; - final SingleObserver<? super Boolean> s; + final SingleObserver<? super Boolean> downstream; final AtomicInteger count; - InnerObserver(int index, CompositeDisposable set, Object[] values, SingleObserver<? super Boolean> s, AtomicInteger count) { + InnerObserver(int index, CompositeDisposable set, Object[] values, SingleObserver<? super Boolean> observer, AtomicInteger count) { this.index = index; this.set = set; this.values = values; - this.s = s; + this.downstream = observer; this.count = count; } + @Override public void onSubscribe(Disposable d) { set.add(d); @@ -67,7 +68,7 @@ public void onSuccess(T value) { values[index] = value; if (count.incrementAndGet() == 2) { - s.onSuccess(ObjectHelper.equals(values[0], values[1])); + downstream.onSuccess(ObjectHelper.equals(values[0], values[1])); } } @@ -81,7 +82,7 @@ public void onError(Throwable e) { } if (count.compareAndSet(state, 2)) { set.dispose(); - s.onError(e); + downstream.onError(e); return; } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleError.java b/src/main/java/io/reactivex/internal/operators/single/SingleError.java index 689070291f..6a6e1aef5a 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleError.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleError.java @@ -29,7 +29,7 @@ public SingleError(Callable<? extends Throwable> errorSupplier) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { + protected void subscribeActual(SingleObserver<? super T> observer) { Throwable error; try { @@ -39,7 +39,7 @@ protected void subscribeActual(SingleObserver<? super T> s) { error = e; } - EmptyDisposable.error(error, s); + EmptyDisposable.error(error, observer); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java index 29dc3433e4..2913ff79d7 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java @@ -32,8 +32,8 @@ public SingleFlatMap(SingleSource<? extends T> source, Function<? super T, ? ext } @Override - protected void subscribeActual(SingleObserver<? super R> actual) { - source.subscribe(new SingleFlatMapCallback<T, R>(actual, mapper)); + protected void subscribeActual(SingleObserver<? super R> downstream) { + source.subscribe(new SingleFlatMapCallback<T, R>(downstream, mapper)); } static final class SingleFlatMapCallback<T, R> @@ -41,13 +41,13 @@ static final class SingleFlatMapCallback<T, R> implements SingleObserver<T>, Disposable { private static final long serialVersionUID = 3258103020495908596L; - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; final Function<? super T, ? extends SingleSource<? extends R>> mapper; SingleFlatMapCallback(SingleObserver<? super R> actual, Function<? super T, ? extends SingleSource<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -64,7 +64,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -76,29 +76,29 @@ public void onSuccess(T value) { o = ObjectHelper.requireNonNull(mapper.apply(value), "The single returned by the mapper is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + downstream.onError(e); return; } if (!isDisposed()) { - o.subscribe(new FlatMapSingleObserver<R>(this, actual)); + o.subscribe(new FlatMapSingleObserver<R>(this, downstream)); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } static final class FlatMapSingleObserver<R> implements SingleObserver<R> { final AtomicReference<Disposable> parent; - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; - FlatMapSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super R> actual) { + FlatMapSingleObserver(AtomicReference<Disposable> parent, SingleObserver<? super R> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -108,12 +108,12 @@ public void onSubscribe(final Disposable d) { @Override public void onSuccess(final R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(final Throwable e) { - actual.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java index 453d8dfeb7..31b9ac2c99 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java @@ -38,9 +38,9 @@ public SingleFlatMapCompletable(SingleSource<T> source, Function<? super T, ? ex } @Override - protected void subscribeActual(CompletableObserver s) { - FlatMapCompletableObserver<T> parent = new FlatMapCompletableObserver<T>(s, mapper); - s.onSubscribe(parent); + protected void subscribeActual(CompletableObserver observer) { + FlatMapCompletableObserver<T> parent = new FlatMapCompletableObserver<T>(observer, mapper); + observer.onSubscribe(parent); source.subscribe(parent); } @@ -50,13 +50,13 @@ static final class FlatMapCompletableObserver<T> private static final long serialVersionUID = -2177128922851101253L; - final CompletableObserver actual; + final CompletableObserver downstream; final Function<? super T, ? extends CompletableSource> mapper; FlatMapCompletableObserver(CompletableObserver actual, Function<? super T, ? extends CompletableSource> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -94,12 +94,12 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java index 7267fad95c..f6f4c79cf5 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java @@ -57,13 +57,13 @@ static final class FlatMapIterableObserver<T, R> private static final long serialVersionUID = -8938804753851907758L; - final Subscriber<? super R> actual; + final Subscriber<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; final AtomicLong requested; - Disposable d; + Disposable upstream; volatile Iterator<? extends R> it; @@ -73,17 +73,17 @@ static final class FlatMapIterableObserver<T, R> FlatMapIterableObserver(Subscriber<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; this.requested = new AtomicLong(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -97,12 +97,12 @@ public void onSuccess(T value) { has = iterator.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } if (!has) { - actual.onComplete(); + downstream.onComplete(); return; } @@ -112,8 +112,8 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override @@ -127,8 +127,8 @@ public void request(long n) { @Override public void cancel() { cancelled = true; - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } void drain() { @@ -136,7 +136,7 @@ void drain() { return; } - Subscriber<? super R> a = actual; + Subscriber<? super R> a = downstream; Iterator<? extends R> iterator = this.it; if (outputFused && iterator != null) { @@ -235,7 +235,6 @@ void slowPath(Subscriber<? super R> a, Iterator<? extends R> iterator) { return; } - boolean b; try { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java index a4d75e5765..b3f822cc91 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java @@ -43,8 +43,8 @@ public SingleFlatMapIterableObservable(SingleSource<T> source, } @Override - protected void subscribeActual(Observer<? super R> s) { - source.subscribe(new FlatMapIterableObserver<T, R>(s, mapper)); + protected void subscribeActual(Observer<? super R> observer) { + source.subscribe(new FlatMapIterableObserver<T, R>(observer, mapper)); } static final class FlatMapIterableObserver<T, R> @@ -53,11 +53,11 @@ static final class FlatMapIterableObserver<T, R> private static final long serialVersionUID = -8938804753851907758L; - final Observer<? super R> actual; + final Observer<? super R> downstream; final Function<? super T, ? extends Iterable<? extends R>> mapper; - Disposable d; + Disposable upstream; volatile Iterator<? extends R> it; @@ -67,22 +67,22 @@ static final class FlatMapIterableObserver<T, R> FlatMapIterableObserver(Observer<? super R> actual, Function<? super T, ? extends Iterable<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - Observer<? super R> a = actual; + Observer<? super R> a = downstream; Iterator<? extends R> iterator; boolean has; try { @@ -91,7 +91,7 @@ public void onSuccess(T value) { has = iterator.hasNext(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } @@ -126,7 +126,6 @@ public void onSuccess(T value) { return; } - boolean b; try { @@ -147,15 +146,15 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; - actual.onError(e); + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); } @Override public void dispose() { cancelled = true; - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java index 2b9030ce19..dfe6b60143 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java @@ -37,8 +37,8 @@ public SingleFlatMapMaybe(SingleSource<? extends T> source, Function<? super T, } @Override - protected void subscribeActual(MaybeObserver<? super R> actual) { - source.subscribe(new FlatMapSingleObserver<T, R>(actual, mapper)); + protected void subscribeActual(MaybeObserver<? super R> downstream) { + source.subscribe(new FlatMapSingleObserver<T, R>(downstream, mapper)); } static final class FlatMapSingleObserver<T, R> @@ -47,12 +47,12 @@ static final class FlatMapSingleObserver<T, R> private static final long serialVersionUID = -5843758257109742742L; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; final Function<? super T, ? extends MaybeSource<? extends R>> mapper; FlatMapSingleObserver(MaybeObserver<? super R> actual, Function<? super T, ? extends MaybeSource<? extends R>> mapper) { - this.actual = actual; + this.downstream = actual; this.mapper = mapper; } @@ -69,7 +69,7 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -86,13 +86,13 @@ public void onSuccess(T value) { } if (!isDisposed()) { - ms.subscribe(new FlatMapMaybeObserver<R>(this, actual)); + ms.subscribe(new FlatMapMaybeObserver<R>(this, downstream)); } } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } @@ -100,11 +100,11 @@ static final class FlatMapMaybeObserver<R> implements MaybeObserver<R> { final AtomicReference<Disposable> parent; - final MaybeObserver<? super R> actual; + final MaybeObserver<? super R> downstream; - FlatMapMaybeObserver(AtomicReference<Disposable> parent, MaybeObserver<? super R> actual) { + FlatMapMaybeObserver(AtomicReference<Disposable> parent, MaybeObserver<? super R> downstream) { this.parent = parent; - this.actual = actual; + this.downstream = downstream; } @Override @@ -114,17 +114,17 @@ public void onSubscribe(final Disposable d) { @Override public void onSuccess(final R value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(final Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void onComplete() { - actual.onComplete(); + downstream.onComplete(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java new file mode 100644 index 0000000000..be090bd20b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import io.reactivex.Flowable; +import io.reactivex.FlowableSubscriber; +import io.reactivex.Scheduler; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * A Flowable that emits items based on applying a specified function to the item emitted by the + * source Single, where that function returns a Publisher. + * <p> + * <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.flatMapPublisher.png" alt=""> + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The returned {@code Flowable} honors the backpressure of the downstream consumer + * and the {@code Publisher} returned by the mapper function is expected to honor it as well.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code flatMapPublisher} does not operate by default on a particular {@link Scheduler}.</dd> + * </dl> + * + * @param <T> the source value type + * @param <R> the result value type + * + * @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a> + * @since 2.1.15 + */ +public final class SingleFlatMapPublisher<T, R> extends Flowable<R> { + + final SingleSource<T> source; + final Function<? super T, ? extends Publisher<? extends R>> mapper; + + public SingleFlatMapPublisher(SingleSource<T> source, + Function<? super T, ? extends Publisher<? extends R>> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber<? super R> downstream) { + source.subscribe(new SingleFlatMapPublisherObserver<T, R>(downstream, mapper)); + } + + static final class SingleFlatMapPublisherObserver<S, T> extends AtomicLong + implements SingleObserver<S>, FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = 7759721921468635667L; + + final Subscriber<? super T> downstream; + final Function<? super S, ? extends Publisher<? extends T>> mapper; + final AtomicReference<Subscription> parent; + Disposable disposable; + + SingleFlatMapPublisherObserver(Subscriber<? super T> actual, + Function<? super S, ? extends Publisher<? extends T>> mapper) { + this.downstream = actual; + this.mapper = mapper; + this.parent = new AtomicReference<Subscription>(); + } + + @Override + public void onSubscribe(Disposable d) { + this.disposable = d; + downstream.onSubscribe(this); + } + + @Override + public void onSuccess(S value) { + Publisher<? extends T> f; + try { + f = ObjectHelper.requireNonNull(mapper.apply(value), "the mapper returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + f.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(parent, this, s); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(parent, this, n); + } + + @Override + public void cancel() { + disposable.dispose(); + SubscriptionHelper.cancel(parent); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java index 588e4bc3c1..2b5ff36a98 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java @@ -16,8 +16,11 @@ import java.util.concurrent.Callable; import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.Exceptions; -import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; public final class SingleFromCallable<T> extends Single<T> { @@ -28,20 +31,29 @@ public SingleFromCallable(Callable<? extends T> callable) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { + protected void subscribeActual(SingleObserver<? super T> observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + + if (d.isDisposed()) { + return; + } + T value; - s.onSubscribe(EmptyDisposable.INSTANCE); try { - T v = callable.call(); - if (v != null) { - s.onSuccess(v); + value = ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); } else { - s.onError(new NullPointerException("The callable returned a null value")); + RxJavaPlugins.onError(ex); } - } catch (Throwable e) { - Exceptions.throwIfFatal(e); - s.onError(e); + return; } - } + if (!d.isDisposed()) { + observer.onSuccess(value); + } + } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java index ecc69b7e92..5709f3c6d4 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java @@ -31,14 +31,14 @@ public SingleFromPublisher(Publisher<? extends T> publisher) { } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - publisher.subscribe(new ToSingleObserver<T>(s)); + protected void subscribeActual(final SingleObserver<? super T> observer) { + publisher.subscribe(new ToSingleObserver<T>(observer)); } static final class ToSingleObserver<T> implements FlowableSubscriber<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; - Subscription s; + Subscription upstream; T value; @@ -46,16 +46,16 @@ static final class ToSingleObserver<T> implements FlowableSubscriber<T>, Disposa volatile boolean disposed; - ToSingleObserver(SingleObserver<? super T> actual) { - this.actual = actual; + ToSingleObserver(SingleObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -67,10 +67,10 @@ public void onNext(T t) { return; } if (value != null) { - s.cancel(); + upstream.cancel(); done = true; this.value = null; - actual.onError(new IndexOutOfBoundsException("Too many elements in the Publisher")); + downstream.onError(new IndexOutOfBoundsException("Too many elements in the Publisher")); } else { value = t; } @@ -84,7 +84,7 @@ public void onError(Throwable t) { } done = true; this.value = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -96,9 +96,9 @@ public void onComplete() { T v = this.value; this.value = null; if (v == null) { - actual.onError(new NoSuchElementException("The source Publisher is empty")); + downstream.onError(new NoSuchElementException("The source Publisher is empty")); } else { - actual.onSuccess(v); + downstream.onSuccess(v); } } @@ -110,7 +110,7 @@ public boolean isDisposed() { @Override public void dispose() { disposed = true; - s.cancel(); + upstream.cancel(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleHide.java b/src/main/java/io/reactivex/internal/operators/single/SingleHide.java index 8415eb76b7..7ec7390e20 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleHide.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleHide.java @@ -26,46 +26,46 @@ public SingleHide(SingleSource<? extends T> source) { } @Override - protected void subscribeActual(SingleObserver<? super T> subscriber) { - source.subscribe(new HideSingleObserver<T>(subscriber)); + protected void subscribeActual(SingleObserver<? super T> observer) { + source.subscribe(new HideSingleObserver<T>(observer)); } static final class HideSingleObserver<T> implements SingleObserver<T>, Disposable { - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; - Disposable d; + Disposable upstream; - HideSingleObserver(SingleObserver<? super T> actual) { - this.actual = actual; + HideSingleObserver(SingleObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void dispose() { - d.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; - actual.onSubscribe(this); + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java index f4ed04d151..7770fd38b4 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java @@ -110,6 +110,7 @@ public Observable apply(SingleSource v) { return new SingleToObservable(v); } } + @SuppressWarnings({ "rawtypes", "unchecked" }) public static <T> Function<SingleSource<? extends T>, Observable<? extends T>> toObservable() { return (Function)ToObservable.INSTANCE; diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleJust.java b/src/main/java/io/reactivex/internal/operators/single/SingleJust.java index 63a0c7fdaa..5e3dfdcd61 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleJust.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleJust.java @@ -25,9 +25,9 @@ public SingleJust(T value) { } @Override - protected void subscribeActual(SingleObserver<? super T> s) { - s.onSubscribe(Disposables.disposed()); - s.onSuccess(value); + protected void subscribeActual(SingleObserver<? super T> observer) { + observer.onSubscribe(Disposables.disposed()); + observer.onSuccess(value); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleLift.java b/src/main/java/io/reactivex/internal/operators/single/SingleLift.java index e618020f44..3d55453b8e 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleLift.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleLift.java @@ -30,14 +30,14 @@ public SingleLift(SingleSource<T> source, SingleOperator<? extends R, ? super T> } @Override - protected void subscribeActual(SingleObserver<? super R> s) { + protected void subscribeActual(SingleObserver<? super R> observer) { SingleObserver<? super T> sr; try { - sr = ObjectHelper.requireNonNull(onLift.apply(s), "The onLift returned a null SingleObserver"); + sr = ObjectHelper.requireNonNull(onLift.apply(observer), "The onLift returned a null SingleObserver"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptyDisposable.error(ex, s); + EmptyDisposable.error(ex, observer); return; } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java new file mode 100644 index 0000000000..e22b64865d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Single source into a single Notification of + * equal kind. + * + * @param <T> the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleMaterialize<T> extends Single<Notification<T>> { + + final Single<T> source; + + public SingleMaterialize(Single<T> source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver<? super Notification<T>> observer) { + source.subscribe(new MaterializeSingleObserver<T>(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleNever.java b/src/main/java/io/reactivex/internal/operators/single/SingleNever.java index 0c01773724..a3c46ebc99 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleNever.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleNever.java @@ -23,8 +23,8 @@ private SingleNever() { } @Override - protected void subscribeActual(SingleObserver<? super Object> s) { - s.onSubscribe(EmptyDisposable.NEVER); + protected void subscribeActual(SingleObserver<? super Object> observer) { + observer.onSubscribe(EmptyDisposable.NEVER); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java index 6d9f3f3331..7c8a6c1977 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java @@ -31,15 +31,15 @@ public SingleObserveOn(SingleSource<T> source, Scheduler scheduler) { } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - source.subscribe(new ObserveOnSingleObserver<T>(s, scheduler)); + protected void subscribeActual(final SingleObserver<? super T> observer) { + source.subscribe(new ObserveOnSingleObserver<T>(observer, scheduler)); } static final class ObserveOnSingleObserver<T> extends AtomicReference<Disposable> implements SingleObserver<T>, Disposable, Runnable { private static final long serialVersionUID = 3528003840217436037L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Scheduler scheduler; @@ -47,14 +47,14 @@ static final class ObserveOnSingleObserver<T> extends AtomicReference<Disposable Throwable error; ObserveOnSingleObserver(SingleObserver<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -76,9 +76,9 @@ public void onError(Throwable e) { public void run() { Throwable ex = error; if (ex != null) { - actual.onError(ex); + downstream.onError(ex); } else { - actual.onSuccess(value); + downstream.onSuccess(value); } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java index d7f9c4c715..0ae695a3e8 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java @@ -32,12 +32,10 @@ public SingleOnErrorReturn(SingleSource<? extends T> source, this.value = value; } - - @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - source.subscribe(new OnErrorReturn(s)); + source.subscribe(new OnErrorReturn(observer)); } final class OnErrorReturn implements SingleObserver<T> { diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java b/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java index 51ab16aca1..325365f8f4 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java @@ -35,34 +35,34 @@ public SingleResumeNext(SingleSource<? extends T> source, } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - source.subscribe(new ResumeMainSingleObserver<T>(s, nextFunction)); + protected void subscribeActual(final SingleObserver<? super T> observer) { + source.subscribe(new ResumeMainSingleObserver<T>(observer, nextFunction)); } static final class ResumeMainSingleObserver<T> extends AtomicReference<Disposable> implements SingleObserver<T>, Disposable { private static final long serialVersionUID = -5314538511045349925L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Function<? super Throwable, ? extends SingleSource<? extends T>> nextFunction; ResumeMainSingleObserver(SingleObserver<? super T> actual, Function<? super Throwable, ? extends SingleSource<? extends T>> nextFunction) { - this.actual = actual; + this.downstream = actual; this.nextFunction = nextFunction; } @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override @@ -73,11 +73,11 @@ public void onError(Throwable e) { source = ObjectHelper.requireNonNull(nextFunction.apply(e), "The nextFunction returned a null SingleSource."); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(new CompositeException(e, ex)); + downstream.onError(new CompositeException(e, ex)); return; } - source.subscribe(new ResumeSingleObserver<T>(this, actual)); + source.subscribe(new ResumeSingleObserver<T>(this, downstream)); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java index 8401704a0a..9b29e6253f 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java @@ -30,9 +30,9 @@ public SingleSubscribeOn(SingleSource<? extends T> source, Scheduler scheduler) } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { - final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s, source); - s.onSubscribe(parent); + protected void subscribeActual(final SingleObserver<? super T> observer) { + final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer, source); + observer.onSubscribe(parent); Disposable f = scheduler.scheduleDirect(parent); @@ -46,14 +46,14 @@ static final class SubscribeOnObserver<T> private static final long serialVersionUID = 7000911171163930287L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final SequentialDisposable task; final SingleSource<? extends T> source; SubscribeOnObserver(SingleObserver<? super T> actual, SingleSource<? extends T> source) { - this.actual = actual; + this.downstream = actual; this.source = source; this.task = new SequentialDisposable(); } @@ -65,12 +65,12 @@ public void onSubscribe(Disposable d) { @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java index 8c340e4edf..8db5e40a03 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java @@ -57,18 +57,19 @@ static final class TakeUntilMainObserver<T> private static final long serialVersionUID = -622603812305745221L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final TakeUntilOtherSubscriber other; - TakeUntilMainObserver(SingleObserver<? super T> actual) { - this.actual = actual; + TakeUntilMainObserver(SingleObserver<? super T> downstream) { + this.downstream = downstream; this.other = new TakeUntilOtherSubscriber(this); } @Override public void dispose() { DisposableHelper.dispose(this); + other.dispose(); } @Override @@ -85,12 +86,9 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { other.dispose(); - Disposable a = get(); + Disposable a = getAndSet(DisposableHelper.DISPOSED); if (a != DisposableHelper.DISPOSED) { - a = getAndSet(DisposableHelper.DISPOSED); - if (a != DisposableHelper.DISPOSED) { - actual.onSuccess(value); - } + downstream.onSuccess(value); } } @@ -102,7 +100,7 @@ public void onError(Throwable e) { if (a != DisposableHelper.DISPOSED) { a = getAndSet(DisposableHelper.DISPOSED); if (a != DisposableHelper.DISPOSED) { - actual.onError(e); + downstream.onError(e); return; } } @@ -117,7 +115,7 @@ void otherError(Throwable e) { if (a != null) { a.dispose(); } - actual.onError(e); + downstream.onError(e); return; } } @@ -139,9 +137,7 @@ static final class TakeUntilOtherSubscriber @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java index 911d7910c6..ea4212f0ec 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java @@ -14,10 +14,14 @@ package io.reactivex.internal.operators.single; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import io.reactivex.*; -import io.reactivex.disposables.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; public final class SingleTimeout<T> extends Single<T> { @@ -41,99 +45,126 @@ public SingleTimeout(SingleSource<T> source, long timeout, TimeUnit unit, Schedu } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { - final CompositeDisposable set = new CompositeDisposable(); - s.onSubscribe(set); + TimeoutMainObserver<T> parent = new TimeoutMainObserver<T>(observer, other, timeout, unit); + observer.onSubscribe(parent); - final AtomicBoolean once = new AtomicBoolean(); + DisposableHelper.replace(parent.task, scheduler.scheduleDirect(parent, timeout, unit)); - Disposable timer = scheduler.scheduleDirect(new TimeoutDispose(once, set, s), timeout, unit); + source.subscribe(parent); + } - set.add(timer); + static final class TimeoutMainObserver<T> extends AtomicReference<Disposable> + implements SingleObserver<T>, Runnable, Disposable { - source.subscribe(new TimeoutObserver(once, set, s)); + private static final long serialVersionUID = 37497744973048446L; - } + final SingleObserver<? super T> downstream; - final class TimeoutDispose implements Runnable { - private final AtomicBoolean once; - final CompositeDisposable set; - final SingleObserver<? super T> s; + final AtomicReference<Disposable> task; - TimeoutDispose(AtomicBoolean once, CompositeDisposable set, SingleObserver<? super T> s) { - this.once = once; - this.set = set; - this.s = s; - } + final TimeoutFallbackObserver<T> fallback; - @Override - public void run() { - if (once.compareAndSet(false, true)) { - if (other != null) { - set.clear(); - other.subscribe(new TimeoutObserver()); - } else { - set.dispose(); - s.onError(new TimeoutException()); - } - } - } + SingleSource<? extends T> other; - final class TimeoutObserver implements SingleObserver<T> { + final long timeout; - @Override - public void onError(Throwable e) { - set.dispose(); - s.onError(e); + final TimeUnit unit; + + static final class TimeoutFallbackObserver<T> extends AtomicReference<Disposable> + implements SingleObserver<T> { + + private static final long serialVersionUID = 2071387740092105509L; + final SingleObserver<? super T> downstream; + + TimeoutFallbackObserver(SingleObserver<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Disposable d) { - set.add(d); + DisposableHelper.setOnce(this, d); } @Override - public void onSuccess(T value) { - set.dispose(); - s.onSuccess(value); + public void onSuccess(T t) { + downstream.onSuccess(t); } + @Override + public void onError(Throwable e) { + downstream.onError(e); + } } - } - final class TimeoutObserver implements SingleObserver<T> { + TimeoutMainObserver(SingleObserver<? super T> actual, SingleSource<? extends T> other, long timeout, TimeUnit unit) { + this.downstream = actual; + this.other = other; + this.timeout = timeout; + this.unit = unit; + this.task = new AtomicReference<Disposable>(); + if (other != null) { + this.fallback = new TimeoutFallbackObserver<T>(actual); + } else { + this.fallback = null; + } + } - private final AtomicBoolean once; - private final CompositeDisposable set; - private final SingleObserver<? super T> s; + @Override + public void run() { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + if (d != null) { + d.dispose(); + } + SingleSource<? extends T> other = this.other; + if (other == null) { + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + } else { + this.other = null; + other.subscribe(fallback); + } + } + } - TimeoutObserver(AtomicBoolean once, CompositeDisposable set, SingleObserver<? super T> s) { - this.once = once; - this.set = set; - this.s = s; + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); } @Override - public void onError(Throwable e) { - if (once.compareAndSet(false, true)) { - set.dispose(); - s.onError(e); + public void onSuccess(T t) { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + DisposableHelper.dispose(task); + downstream.onSuccess(t); } } @Override - public void onSubscribe(Disposable d) { - set.add(d); + public void onError(Throwable e) { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + DisposableHelper.dispose(task); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } } @Override - public void onSuccess(T value) { - if (once.compareAndSet(false, true)) { - set.dispose(); - s.onSuccess(value); + public void dispose() { + DisposableHelper.dispose(this); + DisposableHelper.dispose(task); + if (fallback != null) { + DisposableHelper.dispose(fallback); } } + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java b/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java index 17267aacd2..68b4c9c3b7 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java @@ -36,24 +36,24 @@ public SingleTimer(long delay, TimeUnit unit, Scheduler scheduler) { } @Override - protected void subscribeActual(final SingleObserver<? super Long> s) { - TimerDisposable parent = new TimerDisposable(s); - s.onSubscribe(parent); + protected void subscribeActual(final SingleObserver<? super Long> observer) { + TimerDisposable parent = new TimerDisposable(observer); + observer.onSubscribe(parent); parent.setFuture(scheduler.scheduleDirect(parent, delay, unit)); } static final class TimerDisposable extends AtomicReference<Disposable> implements Disposable, Runnable { private static final long serialVersionUID = 8465401857522493082L; - final SingleObserver<? super Long> actual; + final SingleObserver<? super Long> downstream; - TimerDisposable(final SingleObserver<? super Long> actual) { - this.actual = actual; + TimerDisposable(final SingleObserver<? super Long> downstream) { + this.downstream = downstream; } @Override public void run() { - actual.onSuccess(0L); + downstream.onSuccess(0L); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java index a33d36df98..de2b8a9ed8 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java @@ -40,21 +40,20 @@ public void subscribeActual(final Subscriber<? super T> s) { static final class SingleToFlowableObserver<T> extends DeferredScalarSubscription<T> implements SingleObserver<T> { - private static final long serialVersionUID = 187782011903685568L; - Disposable d; + Disposable upstream; - SingleToFlowableObserver(Subscriber<? super T> actual) { - super(actual); + SingleToFlowableObserver(Subscriber<? super T> downstream) { + super(downstream); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @@ -65,13 +64,13 @@ public void onSuccess(T value) { @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } @Override public void cancel() { super.cancel(); - d.dispose(); + upstream.dispose(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java index b9a5ff9937..2c4126a837 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java @@ -15,6 +15,7 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.DeferredScalarDisposable; /** * Wraps a Single and exposes it as an Observable. @@ -30,49 +31,57 @@ public SingleToObservable(SingleSource<? extends T> source) { } @Override - public void subscribeActual(final Observer<? super T> s) { - source.subscribe(new SingleToObservableObserver<T>(s)); + public void subscribeActual(final Observer<? super T> observer) { + source.subscribe(create(observer)); } - static final class SingleToObservableObserver<T> - implements SingleObserver<T>, Disposable { + /** + * Creates a {@link SingleObserver} wrapper around a {@link Observer}. + * <p>History: 2.0.1 - experimental + * @param <T> the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new SingleObserver instance + * @since 2.2 + */ + public static <T> SingleObserver<T> create(Observer<? super T> downstream) { + return new SingleToObservableObserver<T>(downstream); + } - final Observer<? super T> actual; + static final class SingleToObservableObserver<T> + extends DeferredScalarDisposable<T> + implements SingleObserver<T> { - Disposable d; + private static final long serialVersionUID = 3786543492451018833L; + Disposable upstream; - SingleToObservableObserver(Observer<? super T> actual) { - this.actual = actual; + SingleToObservableObserver(Observer<? super T> downstream) { + super(downstream); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onNext(value); - actual.onComplete(); + complete(value); } @Override public void onError(Throwable e) { - actual.onError(e); + error(e); } @Override public void dispose() { - d.dispose(); + super.dispose(); + upstream.dispose(); } - @Override - public boolean isDisposed() { - return d.isDisposed(); - } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java index 27c4200d8a..dc7962ba8e 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java @@ -45,14 +45,14 @@ static final class UnsubscribeOnSingleObserver<T> extends AtomicReference<Dispos private static final long serialVersionUID = 3256698449646456986L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Scheduler scheduler; Disposable ds; UnsubscribeOnSingleObserver(SingleObserver<? super T> actual, Scheduler scheduler) { - this.actual = actual; + this.downstream = actual; this.scheduler = scheduler; } @@ -78,18 +78,18 @@ public boolean isDisposed() { @Override public void onSubscribe(Disposable d) { if (DisposableHelper.setOnce(this, d)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @Override public void onSuccess(T value) { - actual.onSuccess(value); + downstream.onSuccess(value); } @Override public void onError(Throwable e) { - actual.onError(e); + downstream.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java b/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java index 8ffec7773c..0352bddb63 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java @@ -42,7 +42,7 @@ public SingleUsing(Callable<U> resourceSupplier, } @Override - protected void subscribeActual(final SingleObserver<? super T> s) { + protected void subscribeActual(final SingleObserver<? super T> observer) { final U resource; // NOPMD @@ -50,7 +50,7 @@ protected void subscribeActual(final SingleObserver<? super T> s) { resource = resourceSupplier.call(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - EmptyDisposable.error(ex, s); + EmptyDisposable.error(ex, observer); return; } @@ -69,7 +69,7 @@ protected void subscribeActual(final SingleObserver<? super T> s) { ex = new CompositeException(ex, exc); } } - EmptyDisposable.error(ex, s); + EmptyDisposable.error(ex, observer); if (!eager) { try { disposer.accept(resource); @@ -81,7 +81,7 @@ protected void subscribeActual(final SingleObserver<? super T> s) { return; } - source.subscribe(new UsingSingleObserver<T, U>(s, resource, eager, disposer)); + source.subscribe(new UsingSingleObserver<T, U>(observer, resource, eager, disposer)); } static final class UsingSingleObserver<T, U> extends @@ -89,47 +89,47 @@ static final class UsingSingleObserver<T, U> extends private static final long serialVersionUID = -5331524057054083935L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; final Consumer<? super U> disposer; final boolean eager; - Disposable d; + Disposable upstream; UsingSingleObserver(SingleObserver<? super T> actual, U resource, boolean eager, Consumer<? super U> disposer) { super(resource); - this.actual = actual; + this.downstream = actual; this.eager = eager; this.disposer = disposer; } @Override public void dispose() { - d.dispose(); - d = DisposableHelper.DISPOSED; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; disposeAfter(); } @Override public boolean isDisposed() { - return d.isDisposed(); + return upstream.isDisposed(); } @Override public void onSubscribe(Disposable d) { - if (DisposableHelper.validate(this.d, d)) { - this.d = d; + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } @SuppressWarnings("unchecked") @Override public void onSuccess(T value) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object u = getAndSet(this); @@ -138,7 +138,7 @@ public void onSuccess(T value) { disposer.accept((U)u); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } } else { @@ -146,7 +146,7 @@ public void onSuccess(T value) { } } - actual.onSuccess(value); + downstream.onSuccess(value); if (!eager) { disposeAfter(); @@ -156,7 +156,7 @@ public void onSuccess(T value) { @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { - d = DisposableHelper.DISPOSED; + upstream = DisposableHelper.DISPOSED; if (eager) { Object u = getAndSet(this); @@ -172,7 +172,7 @@ public void onError(Throwable e) { } } - actual.onError(e); + downstream.onError(e); if (!eager) { disposeAfter(); diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java index e98917b511..31fd470ecd 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java @@ -39,7 +39,6 @@ protected void subscribeActual(SingleObserver<? super R> observer) { SingleSource<? extends T>[] sources = this.sources; int n = sources.length; - if (n == 1) { sources[0].subscribe(new SingleMap.MapSingleObserver<T, R>(observer, new SingletonArrayFunc())); return; @@ -67,10 +66,9 @@ protected void subscribeActual(SingleObserver<? super R> observer) { static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposable { - private static final long serialVersionUID = -5556924161382950569L; - final SingleObserver<? super R> actual; + final SingleObserver<? super R> downstream; final Function<? super Object[], ? extends R> zipper; @@ -81,7 +79,7 @@ static final class ZipCoordinator<T, R> extends AtomicInteger implements Disposa @SuppressWarnings("unchecked") ZipCoordinator(SingleObserver<? super R> observer, int n, Function<? super Object[], ? extends R> zipper) { super(n); - this.actual = observer; + this.downstream = observer; this.zipper = zipper; ZipSingleObserver<T>[] o = new ZipSingleObserver[n]; for (int i = 0; i < n; i++) { @@ -114,11 +112,11 @@ void innerSuccess(T value, int index) { v = ObjectHelper.requireNonNull(zipper.apply(values), "The zipper returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - actual.onError(ex); + downstream.onError(ex); return; } - actual.onSuccess(v); + downstream.onSuccess(v); } } @@ -136,7 +134,7 @@ void disposeExcept(int index) { void innerError(Throwable ex, int index) { if (getAndSet(0) > 0) { disposeExcept(index); - actual.onError(ex); + downstream.onError(ex); } else { RxJavaPlugins.onError(ex); } diff --git a/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java index eddfb306b4..c33147de06 100644 --- a/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java +++ b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java @@ -36,7 +36,7 @@ public MpscLinkedQueue() { consumerNode = new AtomicReference<LinkedQueueNode<T>>(); LinkedQueueNode<T> node = new LinkedQueueNode<T>(); spConsumerNode(node); - xchgProducerNode(node);// this ensures correct construction: StoreLoad + xchgProducerNode(node); // this ensures correct construction: StoreLoad } /** diff --git a/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java index 1afa99738f..53ac212600 100644 --- a/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java +++ b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java @@ -89,12 +89,12 @@ public E poll() { final long index = consumerIndex.get(); final int offset = calcElementOffset(index); // local load of field to avoid repeated loads after volatile reads - final E e = lvElement(offset);// LoadLoad + final E e = lvElement(offset); // LoadLoad if (null == e) { return null; } soConsumerIndex(index + 1); // ordered store -> atomic and ordered for size() - soElement(offset, null);// StoreStore + soElement(offset, null); // StoreStore return e; } diff --git a/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java index f386ac998f..e5e71e29f4 100644 --- a/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java +++ b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java @@ -92,8 +92,8 @@ public boolean offer(final T e) { } private boolean writeToQueue(final AtomicReferenceArray<Object> buffer, final T e, final long index, final int offset) { - soElement(buffer, offset, e);// StoreStore - soProducerIndex(index + 1);// this ensures atomic write of long on 32bit platforms + soElement(buffer, offset, e); // StoreStore + soProducerIndex(index + 1); // this ensures atomic write of long on 32bit platforms return true; } @@ -103,19 +103,23 @@ private void resize(final AtomicReferenceArray<Object> oldBuffer, final long cur final AtomicReferenceArray<Object> newBuffer = new AtomicReferenceArray<Object>(capacity); producerBuffer = newBuffer; producerLookAhead = currIndex + mask - 1; - soElement(newBuffer, offset, e);// StoreStore + soElement(newBuffer, offset, e); // StoreStore soNext(oldBuffer, newBuffer); soElement(oldBuffer, offset, HAS_NEXT); // new buffer is visible after element is // inserted - soProducerIndex(currIndex + 1);// this ensures correctness on 32bit platforms + soProducerIndex(currIndex + 1); // this ensures correctness on 32bit platforms } private void soNext(AtomicReferenceArray<Object> curr, AtomicReferenceArray<Object> next) { soElement(curr, calcDirectOffset(curr.length() - 1), next); } + @SuppressWarnings("unchecked") - private AtomicReferenceArray<Object> lvNext(AtomicReferenceArray<Object> curr) { - return (AtomicReferenceArray<Object>)lvElement(curr, calcDirectOffset(curr.length() - 1)); + private AtomicReferenceArray<Object> lvNextBufferAndUnlink(AtomicReferenceArray<Object> curr, int nextIndex) { + int nextOffset = calcDirectOffset(nextIndex); + AtomicReferenceArray<Object> nextBuffer = (AtomicReferenceArray<Object>)lvElement(curr, nextOffset); + soElement(curr, nextOffset, null); // Avoid GC nepotism + return nextBuffer; } /** * {@inheritDoc} @@ -131,14 +135,14 @@ public T poll() { final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); - final Object e = lvElement(buffer, offset);// LoadLoad + final Object e = lvElement(buffer, offset); // LoadLoad boolean isNextBuffer = e == HAS_NEXT; if (null != e && !isNextBuffer) { - soElement(buffer, offset, null);// StoreStore - soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms + soElement(buffer, offset, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms return (T) e; } else if (isNextBuffer) { - return newBufferPoll(lvNext(buffer), index, mask); + return newBufferPoll(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); } return null; @@ -148,10 +152,10 @@ public T poll() { private T newBufferPoll(AtomicReferenceArray<Object> nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); - final T n = (T) lvElement(nextBuffer, offsetInNew);// LoadLoad + final T n = (T) lvElement(nextBuffer, offsetInNew); // LoadLoad if (null != n) { - soElement(nextBuffer, offsetInNew, null);// StoreStore - soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms + soElement(nextBuffer, offsetInNew, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms } return n; } @@ -162,9 +166,9 @@ public T peek() { final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); - final Object e = lvElement(buffer, offset);// LoadLoad + final Object e = lvElement(buffer, offset); // LoadLoad if (e == HAS_NEXT) { - return newBufferPeek(lvNext(buffer), index, mask); + return newBufferPeek(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); } return (T) e; @@ -174,8 +178,9 @@ public T peek() { private T newBufferPeek(AtomicReferenceArray<Object> nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); - return (T) lvElement(nextBuffer, offsetInNew);// LoadLoad + return (T) lvElement(nextBuffer, offsetInNew); // LoadLoad } + @Override public void clear() { while (poll() != null || !isEmpty()) { } // NOPMD @@ -272,13 +277,13 @@ public boolean offer(T first, T second) { producerBuffer = newBuffer; pi = calcWrappedOffset(p, m); - soElement(newBuffer, pi + 1, second);// StoreStore + soElement(newBuffer, pi + 1, second); // StoreStore soElement(newBuffer, pi, first); soNext(buffer, newBuffer); soElement(buffer, pi, HAS_NEXT); // new buffer is visible after element is - soProducerIndex(p + 2);// this ensures correctness on 32bit platforms + soProducerIndex(p + 2); // this ensures correctness on 32bit platforms } return true; diff --git a/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java b/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java index cd278fe590..2bf3c454f4 100644 --- a/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java +++ b/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java @@ -21,6 +21,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.internal.functions.Functions; +import io.reactivex.schedulers.SchedulerRunnableIntrospection; /** * Base functionality for direct tasks that manage a runnable and cancellation/completion. @@ -28,7 +29,7 @@ */ abstract class AbstractDirectTask extends AtomicReference<Future<?>> -implements Disposable { +implements Disposable, SchedulerRunnableIntrospection { private static final long serialVersionUID = 1811839108042568751L; @@ -77,4 +78,9 @@ public final void setFuture(Future<?> future) { } } } + + @Override + public Runnable getWrappedRunnable() { + return runnable; + } } diff --git a/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java index f2a4dcf7d3..6f10ab2867 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java @@ -15,19 +15,20 @@ */ package io.reactivex.internal.schedulers; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + import io.reactivex.Scheduler; import io.reactivex.annotations.NonNull; import io.reactivex.disposables.*; import io.reactivex.internal.disposables.*; - -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import io.reactivex.internal.functions.ObjectHelper; /** * Holds a fixed pool of worker threads and assigns them * to requested Scheduler.Workers in a round-robin fashion. */ -public final class ComputationScheduler extends Scheduler { +public final class ComputationScheduler extends Scheduler implements SchedulerMultiWorkerSupport { /** This will indicate no pool is active. */ static final FixedSchedulerPool NONE; /** Manages a fixed number of workers. */ @@ -67,7 +68,7 @@ static int cap(int cpuCount, int paramThreads) { return paramThreads <= 0 || paramThreads > cpuCount ? cpuCount : paramThreads; } - static final class FixedSchedulerPool { + static final class FixedSchedulerPool implements SchedulerMultiWorkerSupport { final int cores; final PoolWorker[] eventLoops; @@ -96,6 +97,25 @@ public void shutdown() { w.dispose(); } } + + @Override + public void createWorkers(int number, WorkerCallback callback) { + int c = cores; + if (c == 0) { + for (int i = 0; i < number; i++) { + callback.onWorker(i, SHUTDOWN_WORKER); + } + } else { + int index = (int)n % c; + for (int i = 0; i < number; i++) { + callback.onWorker(i, new EventLoopWorker(eventLoops[index])); + if (++index == c) { + index = 0; + } + } + n = index; + } + } } /** @@ -125,6 +145,12 @@ public Worker createWorker() { return new EventLoopWorker(pool.get().getEventLoop()); } + @Override + public void createWorkers(int number, WorkerCallback callback) { + ObjectHelper.verifyPositive(number, "number > 0 required"); + pool.get().createWorkers(number, callback); + } + @NonNull @Override public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { @@ -161,7 +187,6 @@ public void shutdown() { } } - static final class EventLoopWorker extends Scheduler.Worker { private final ListCompositeDisposable serial; private final CompositeDisposable timed; @@ -201,6 +226,7 @@ public Disposable schedule(@NonNull Runnable action) { return poolWorker.scheduleActual(action, 0, TimeUnit.MILLISECONDS, serial); } + @NonNull @Override public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) { diff --git a/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java b/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java index ef7dfedd02..c79eaeb149 100644 --- a/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java +++ b/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java @@ -22,15 +22,16 @@ * the other methods are not implemented. */ final class DisposeOnCancel implements Future<Object> { - final Disposable d; + + final Disposable upstream; DisposeOnCancel(Disposable d) { - this.d = d; + this.upstream = d; } @Override public boolean cancel(boolean mayInterruptIfRunning) { - d.dispose(); + upstream.dispose(); return false; } diff --git a/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java index e050fed1f7..75322a8de8 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java @@ -20,29 +20,33 @@ import io.reactivex.annotations.NonNull; import io.reactivex.disposables.*; import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.queue.MpscLinkedQueue; -import io.reactivex.internal.schedulers.ExecutorScheduler.ExecutorWorker.BooleanRunnable; +import io.reactivex.internal.schedulers.ExecutorScheduler.ExecutorWorker.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.schedulers.*; /** * Wraps an Executor and provides the Scheduler API over it. */ public final class ExecutorScheduler extends Scheduler { + final boolean interruptibleWorker; + @NonNull final Executor executor; static final Scheduler HELPER = Schedulers.single(); - public ExecutorScheduler(@NonNull Executor executor) { + public ExecutorScheduler(@NonNull Executor executor, boolean interruptibleWorker) { this.executor = executor; + this.interruptibleWorker = interruptibleWorker; } @NonNull @Override public Worker createWorker() { - return new ExecutorWorker(executor); + return new ExecutorWorker(executor, interruptibleWorker); } @NonNull @@ -57,9 +61,15 @@ public Disposable scheduleDirect(@NonNull Runnable run) { return task; } - BooleanRunnable br = new BooleanRunnable(decoratedRun); - executor.execute(br); - return br; + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, null); + executor.execute(interruptibleTask); + return interruptibleTask; + } else { + BooleanRunnable br = new BooleanRunnable(decoratedRun); + executor.execute(br); + return br; + } } catch (RejectedExecutionException ex) { RxJavaPlugins.onError(ex); return EmptyDisposable.INSTANCE; @@ -110,6 +120,9 @@ public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initial } /* public: test support. */ public static final class ExecutorWorker extends Scheduler.Worker implements Runnable { + + final boolean interruptibleWorker; + final Executor executor; final MpscLinkedQueue<Runnable> queue; @@ -120,9 +133,10 @@ public static final class ExecutorWorker extends Scheduler.Worker implements Run final CompositeDisposable tasks = new CompositeDisposable(); - public ExecutorWorker(Executor executor) { + public ExecutorWorker(Executor executor, boolean interruptibleWorker) { this.executor = executor; this.queue = new MpscLinkedQueue<Runnable>(); + this.interruptibleWorker = interruptibleWorker; } @NonNull @@ -133,9 +147,24 @@ public Disposable schedule(@NonNull Runnable run) { } Runnable decoratedRun = RxJavaPlugins.onSchedule(run); - BooleanRunnable br = new BooleanRunnable(decoratedRun); - queue.offer(br); + Runnable task; + Disposable disposable; + + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, tasks); + tasks.add(interruptibleTask); + + task = interruptibleTask; + disposable = interruptibleTask; + } else { + BooleanRunnable runnableTask = new BooleanRunnable(decoratedRun); + + task = runnableTask; + disposable = runnableTask; + } + + queue.offer(task); if (wip.getAndIncrement() == 0) { try { @@ -148,7 +177,7 @@ public Disposable schedule(@NonNull Runnable run) { } } - return br; + return disposable; } @NonNull @@ -161,7 +190,6 @@ public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit return EmptyDisposable.INSTANCE; } - SequentialDisposable first = new SequentialDisposable(); final SequentialDisposable mar = new SequentialDisposable(first); @@ -288,9 +316,101 @@ public void run() { mar.replace(schedule(decoratedRun)); } } + + /** + * Wrapper for a {@link Runnable} with additional logic for handling interruption on + * a shared thread, similar to how Java Executors do it. + */ + static final class InterruptibleRunnable extends AtomicInteger implements Runnable, Disposable { + + private static final long serialVersionUID = -3603436687413320876L; + + final Runnable run; + + final DisposableContainer tasks; + + volatile Thread thread; + + static final int READY = 0; + + static final int RUNNING = 1; + + static final int FINISHED = 2; + + static final int INTERRUPTING = 3; + + static final int INTERRUPTED = 4; + + InterruptibleRunnable(Runnable run, DisposableContainer tasks) { + this.run = run; + this.tasks = tasks; + } + + @Override + public void run() { + if (get() == READY) { + thread = Thread.currentThread(); + if (compareAndSet(READY, RUNNING)) { + try { + run.run(); + } finally { + thread = null; + if (compareAndSet(RUNNING, FINISHED)) { + cleanup(); + } else { + while (get() == INTERRUPTING) { + Thread.yield(); + } + Thread.interrupted(); + } + } + } else { + thread = null; + } + } + } + + @Override + public void dispose() { + for (;;) { + int state = get(); + if (state >= FINISHED) { + break; + } else if (state == READY) { + if (compareAndSet(READY, INTERRUPTED)) { + cleanup(); + break; + } + } else { + if (compareAndSet(RUNNING, INTERRUPTING)) { + Thread t = thread; + if (t != null) { + t.interrupt(); + thread = null; + } + set(INTERRUPTED); + cleanup(); + break; + } + } + } + } + + void cleanup() { + if (tasks != null) { + tasks.delete(this); + } + } + + @Override + public boolean isDisposed() { + return get() >= FINISHED; + } + } } - static final class DelayedRunnable extends AtomicReference<Runnable> implements Runnable, Disposable { + static final class DelayedRunnable extends AtomicReference<Runnable> + implements Runnable, Disposable, SchedulerRunnableIntrospection { private static final long serialVersionUID = -4101336210206799084L; @@ -330,6 +450,12 @@ public void dispose() { direct.dispose(); } } + + @Override + public Runnable getWrappedRunnable() { + Runnable r = get(); + return r != null ? r : Functions.EMPTY_RUNNABLE; + } } final class DelayedDispose implements Runnable { diff --git a/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java b/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java index 86086987ab..ec9b2a0d2f 100644 --- a/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java +++ b/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java @@ -50,16 +50,14 @@ final class InstantPeriodicTask implements Callable<Void>, Disposable { @Override public Void call() throws Exception { + runner = Thread.currentThread(); try { - runner = Thread.currentThread(); - try { - task.run(); - setRest(executor.submit(this)); - } catch (Throwable ex) { - RxJavaPlugins.onError(ex); - } - } finally { + task.run(); + setRest(executor.submit(this)); + runner = null; + } catch (Throwable ex) { runner = null; + RxJavaPlugins.onError(ex); } return null; } @@ -86,6 +84,7 @@ void setFirst(Future<?> f) { Future<?> current = first.get(); if (current == CANCELLED) { f.cancel(runner != Thread.currentThread()); + return; } if (first.compareAndSet(current, f)) { return; @@ -98,6 +97,7 @@ void setRest(Future<?> f) { Future<?> current = rest.get(); if (current == CANCELLED) { f.cancel(runner != Thread.currentThread()); + return; } if (rest.compareAndSet(current, f)) { return; diff --git a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java index f6913020a5..d806321bf3 100644 --- a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java @@ -34,7 +34,11 @@ public final class IoScheduler extends Scheduler { private static final String EVICTOR_THREAD_NAME_PREFIX = "RxCachedWorkerPoolEvictor"; static final RxThreadFactory EVICTOR_THREAD_FACTORY; - private static final long KEEP_ALIVE_TIME = 60; + /** The name of the system property for setting the keep-alive time (in seconds) for this Scheduler workers. */ + private static final String KEY_KEEP_ALIVE_TIME = "rx2.io-keep-alive-time"; + public static final long KEEP_ALIVE_TIME_DEFAULT = 60; + + private static final long KEEP_ALIVE_TIME; private static final TimeUnit KEEP_ALIVE_UNIT = TimeUnit.SECONDS; static final ThreadWorker SHUTDOWN_THREAD_WORKER; @@ -45,7 +49,10 @@ public final class IoScheduler extends Scheduler { private static final String KEY_IO_PRIORITY = "rx2.io-priority"; static final CachedWorkerPool NONE; + static { + KEEP_ALIVE_TIME = Long.getLong(KEY_KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_DEFAULT); + SHUTDOWN_THREAD_WORKER = new ThreadWorker(new RxThreadFactory("RxCachedThreadSchedulerShutdown")); SHUTDOWN_THREAD_WORKER.dispose(); @@ -151,6 +158,7 @@ public IoScheduler() { } /** + * Constructs an IoScheduler with the given thread factory and starts the pool of workers. * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. */ @@ -167,6 +175,7 @@ public void start() { update.shutdown(); } } + @Override public void shutdown() { for (;;) { diff --git a/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java index 3499168f71..9ef225aceb 100644 --- a/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java +++ b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java @@ -116,7 +116,6 @@ public Disposable schedulePeriodicallyDirect(Runnable run, long initialDelay, lo } } - /** * Wraps the given runnable into a ScheduledRunnable and schedules it * on the underlying ScheduledExecutorService. diff --git a/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java index 080928f722..201847ba75 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java +++ b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java @@ -35,14 +35,12 @@ public ScheduledDirectPeriodicTask(Runnable runnable) { public void run() { runner = Thread.currentThread(); try { - try { - runnable.run(); - } catch (Throwable ex) { - lazySet(FINISHED); - RxJavaPlugins.onError(ex); - } - } finally { + runnable.run(); runner = null; + } catch (Throwable ex) { + runner = null; + lazySet(FINISHED); + RxJavaPlugins.onError(ex); } } } diff --git a/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java b/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java index c942deacfa..61218fbe40 100644 --- a/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java +++ b/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java @@ -26,7 +26,12 @@ public final class ScheduledRunnable extends AtomicReferenceArray<Object> private static final long serialVersionUID = -6120223772001106981L; final Runnable actual; - static final Object DISPOSED = new Object(); + /** Indicates that the parent tracking this task has been notified about its completion. */ + static final Object PARENT_DISPOSED = new Object(); + /** Indicates the dispose() was called from within the run/call method. */ + static final Object SYNC_DISPOSED = new Object(); + /** Indicates the dispose() was called from another thread. */ + static final Object ASYNC_DISPOSED = new Object(); static final Object DONE = new Object(); @@ -66,13 +71,13 @@ public void run() { } finally { lazySet(THREAD_INDEX, null); Object o = get(PARENT_INDEX); - if (o != DISPOSED && o != null && compareAndSet(PARENT_INDEX, o, DONE)) { + if (o != PARENT_DISPOSED && compareAndSet(PARENT_INDEX, o, DONE) && o != null) { ((DisposableContainer)o).delete(this); } for (;;) { o = get(FUTURE_INDEX); - if (o == DISPOSED || compareAndSet(FUTURE_INDEX, o, DONE)) { + if (o == SYNC_DISPOSED || o == ASYNC_DISPOSED || compareAndSet(FUTURE_INDEX, o, DONE)) { break; } } @@ -85,8 +90,12 @@ public void setFuture(Future<?> f) { if (o == DONE) { return; } - if (o == DISPOSED) { - f.cancel(get(THREAD_INDEX) != Thread.currentThread()); + if (o == SYNC_DISPOSED) { + f.cancel(false); + return; + } + if (o == ASYNC_DISPOSED) { + f.cancel(true); return; } if (compareAndSet(FUTURE_INDEX, o, f)) { @@ -99,12 +108,13 @@ public void setFuture(Future<?> f) { public void dispose() { for (;;) { Object o = get(FUTURE_INDEX); - if (o == DONE || o == DISPOSED) { + if (o == DONE || o == SYNC_DISPOSED || o == ASYNC_DISPOSED) { break; } - if (compareAndSet(FUTURE_INDEX, o, DISPOSED)) { + boolean async = get(THREAD_INDEX) != Thread.currentThread(); + if (compareAndSet(FUTURE_INDEX, o, async ? ASYNC_DISPOSED : SYNC_DISPOSED)) { if (o != null) { - ((Future<?>)o).cancel(get(THREAD_INDEX) != Thread.currentThread()); + ((Future<?>)o).cancel(async); } break; } @@ -112,10 +122,10 @@ public void dispose() { for (;;) { Object o = get(PARENT_INDEX); - if (o == DONE || o == DISPOSED || o == null) { + if (o == DONE || o == PARENT_DISPOSED || o == null) { return; } - if (compareAndSet(PARENT_INDEX, o, DISPOSED)) { + if (compareAndSet(PARENT_INDEX, o, PARENT_DISPOSED)) { ((DisposableContainer)o).delete(this); return; } @@ -124,7 +134,7 @@ public void dispose() { @Override public boolean isDisposed() { - Object o = get(FUTURE_INDEX); - return o == DISPOSED || o == DONE; + Object o = get(PARENT_INDEX); + return o == PARENT_DISPOSED || o == DONE; } } diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java new file mode 100644 index 0000000000..9eec7eef0c --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.schedulers; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.*; + +/** + * Allows retrieving multiple workers from the implementing + * {@link io.reactivex.Scheduler} in a way that when asking for + * at most the parallelism level of the Scheduler, those + * {@link io.reactivex.Scheduler.Worker} instances will be running + * with different backing threads. + * <p>History: 2.1.8 - experimental + * @since 2.2 + */ +public interface SchedulerMultiWorkerSupport { + + /** + * Creates the given number of {@link io.reactivex.Scheduler.Worker} instances + * that are possibly backed by distinct threads + * and calls the specified {@code Consumer} with them. + * @param number the number of workers to create, positive + * @param callback the callback to send worker instances to + */ + void createWorkers(int number, @NonNull WorkerCallback callback); + + /** + * The callback interface for the {@link SchedulerMultiWorkerSupport#createWorkers(int, WorkerCallback)} + * method. + */ + interface WorkerCallback { + /** + * Called with the Worker index and instance. + * @param index the worker index, zero-based + * @param worker the worker instance + */ + void onWorker(int index, @NonNull Scheduler.Worker worker); + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java index 7aa5bb6a19..ab465046aa 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java @@ -20,7 +20,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.functions.Function; /** * Manages the creating of ScheduledExecutorServices and sets up purging. @@ -57,22 +57,25 @@ private SchedulerPoolFactory() { * Starts the purge thread if not already started. */ public static void start() { - if (!PURGE_ENABLED) { - return; - } - for (;;) { - ScheduledExecutorService curr = PURGE_THREAD.get(); - if (curr != null && !curr.isShutdown()) { - return; - } - ScheduledExecutorService next = Executors.newScheduledThreadPool(1, new RxThreadFactory("RxSchedulerPurge")); - if (PURGE_THREAD.compareAndSet(curr, next)) { + tryStart(PURGE_ENABLED); + } - next.scheduleAtFixedRate(new ScheduledTask(), PURGE_PERIOD_SECONDS, PURGE_PERIOD_SECONDS, TimeUnit.SECONDS); + static void tryStart(boolean purgeEnabled) { + if (purgeEnabled) { + for (;;) { + ScheduledExecutorService curr = PURGE_THREAD.get(); + if (curr != null) { + return; + } + ScheduledExecutorService next = Executors.newScheduledThreadPool(1, new RxThreadFactory("RxSchedulerPurge")); + if (PURGE_THREAD.compareAndSet(curr, next)) { - return; - } else { - next.shutdownNow(); + next.scheduleAtFixedRate(new ScheduledTask(), PURGE_PERIOD_SECONDS, PURGE_PERIOD_SECONDS, TimeUnit.SECONDS); + + return; + } else { + next.shutdownNow(); + } } } } @@ -81,7 +84,7 @@ public static void start() { * Stops the purge thread. */ public static void shutdown() { - ScheduledExecutorService exec = PURGE_THREAD.get(); + ScheduledExecutorService exec = PURGE_THREAD.getAndSet(null); if (exec != null) { exec.shutdownNow(); } @@ -89,23 +92,48 @@ public static void shutdown() { } static { - boolean purgeEnable = true; - int purgePeriod = 1; + SystemPropertyAccessor propertyAccessor = new SystemPropertyAccessor(); + PURGE_ENABLED = getBooleanProperty(true, PURGE_ENABLED_KEY, true, true, propertyAccessor); + PURGE_PERIOD_SECONDS = getIntProperty(PURGE_ENABLED, PURGE_PERIOD_SECONDS_KEY, 1, 1, propertyAccessor); - Properties properties = System.getProperties(); + start(); + } - if (properties.containsKey(PURGE_ENABLED_KEY)) { - purgeEnable = Boolean.getBoolean(PURGE_ENABLED_KEY); + static int getIntProperty(boolean enabled, String key, int defaultNotFound, int defaultNotEnabled, Function<String, String> propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; + } + return Integer.parseInt(value); + } catch (Throwable ex) { + return defaultNotFound; + } } + return defaultNotEnabled; + } - if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { - purgePeriod = Integer.getInteger(PURGE_PERIOD_SECONDS_KEY, purgePeriod); + static boolean getBooleanProperty(boolean enabled, String key, boolean defaultNotFound, boolean defaultNotEnabled, Function<String, String> propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; + } + return "true".equals(value); + } catch (Throwable ex) { + return defaultNotFound; + } } + return defaultNotEnabled; + } - PURGE_ENABLED = purgeEnable; - PURGE_PERIOD_SECONDS = purgePeriod; - - start(); + static final class SystemPropertyAccessor implements Function<String, String> { + @Override + public String apply(String t) throws Exception { + return System.getProperty(t); + } } /** @@ -115,27 +143,26 @@ public static void shutdown() { */ public static ScheduledExecutorService create(ThreadFactory factory) { final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory); - if (PURGE_ENABLED && exec instanceof ScheduledThreadPoolExecutor) { + tryPutIntoPool(PURGE_ENABLED, exec); + return exec; + } + + static void tryPutIntoPool(boolean purgeEnabled, ScheduledExecutorService exec) { + if (purgeEnabled && exec instanceof ScheduledThreadPoolExecutor) { ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec; POOLS.put(e, exec); } - return exec; } static final class ScheduledTask implements Runnable { @Override public void run() { - try { - for (ScheduledThreadPoolExecutor e : new ArrayList<ScheduledThreadPoolExecutor>(POOLS.keySet())) { - if (e.isShutdown()) { - POOLS.remove(e); - } else { - e.purge(); - } + for (ScheduledThreadPoolExecutor e : new ArrayList<ScheduledThreadPoolExecutor>(POOLS.keySet())) { + if (e.isShutdown()) { + POOLS.remove(e); + } else { + e.purge(); } - } catch (Throwable e) { - // Exceptions.throwIfFatal(e); nowhere to go - RxJavaPlugins.onError(e); } } } diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java index dd91cc11d7..4d56335527 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java @@ -24,12 +24,11 @@ import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Scheduler; -import io.reactivex.annotations.Experimental; import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; -import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; +import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.processors.FlowableProcessor; import io.reactivex.processors.UnicastProcessor; @@ -100,8 +99,9 @@ * })); * }); * </pre> + * <p>History 2.0.1 - experimental + * @since 2.1 */ -@Experimental public class SchedulerWhen extends Scheduler implements Disposable { private final Scheduler actualScheduler; private final FlowableProcessor<Flowable<Completable>> workerProcessor; @@ -116,7 +116,7 @@ public SchedulerWhen(Function<Flowable<Flowable<Completable>>, Completable> comb try { disposable = combine.apply(workerProcessor).subscribe(); } catch (Throwable e) { - Exceptions.propagate(e); + throw ExceptionHelper.wrapOrThrow(e); } } @@ -155,7 +155,7 @@ public Worker createWorker() { static final Disposable DISPOSED = Disposables.disposed(); @SuppressWarnings("serial") - abstract static class ScheduledAction extends AtomicReference<Disposable>implements Disposable { + abstract static class ScheduledAction extends AtomicReference<Disposable> implements Disposable { ScheduledAction() { super(SUBSCRIBED); } diff --git a/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java index d65348aa3c..40a7ce0b85 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java @@ -53,6 +53,8 @@ public SingleScheduler() { } /** + * Constructs a SingleScheduler with the given ThreadFactory and prepares the + * single scheduler thread. * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. */ diff --git a/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java index 2f9a595f6d..49a053f9db 100644 --- a/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java @@ -49,7 +49,7 @@ public Worker createWorker() { @NonNull @Override public Disposable scheduleDirect(@NonNull Runnable run) { - run.run(); + RxJavaPlugins.onSchedule(run).run(); return EmptyDisposable.INSTANCE; } @@ -58,7 +58,7 @@ public Disposable scheduleDirect(@NonNull Runnable run) { public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { try { unit.sleep(delay); - run.run(); + RxJavaPlugins.onSchedule(run).run(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); RxJavaPlugins.onError(ex); @@ -190,14 +190,12 @@ public void run() { long t = worker.now(TimeUnit.MILLISECONDS); if (execTime > t) { long delay = execTime - t; - if (delay > 0) { - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - RxJavaPlugins.onError(e); - return; - } + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + RxJavaPlugins.onError(e); + return; } } diff --git a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java index c9d2f98b46..f665d48a49 100644 --- a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java @@ -28,10 +28,10 @@ public abstract class BasicFuseableConditionalSubscriber<T, R> implements ConditionalSubscriber<T>, QueueSubscription<R> { /** The downstream subscriber. */ - protected final ConditionalSubscriber<? super R> actual; + protected final ConditionalSubscriber<? super R> downstream; /** The upstream subscription. */ - protected Subscription s; + protected Subscription upstream; /** The upstream's QueueSubscription if not null. */ protected QueueSubscription<T> qs; @@ -44,26 +44,26 @@ public abstract class BasicFuseableConditionalSubscriber<T, R> implements Condit /** * Construct a BasicFuseableSubscriber by wrapping the given subscriber. - * @param actual the subscriber, not null (not verified) + * @param downstream the subscriber, not null (not verified) */ - public BasicFuseableConditionalSubscriber(ConditionalSubscriber<? super R> actual) { - this.actual = actual; + public BasicFuseableConditionalSubscriber(ConditionalSubscriber<? super R> downstream) { + this.downstream = downstream; } // final: fixed protocol steps to support fuseable and non-fuseable upstream @SuppressWarnings("unchecked") @Override public final void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { - this.s = s; + this.upstream = s; if (s instanceof QueueSubscription) { this.qs = (QueueSubscription<T>)s; } if (beforeDownstream()) { - actual.onSubscribe(this); + downstream.onSubscribe(this); afterDownstream(); } @@ -97,7 +97,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } /** @@ -106,7 +106,7 @@ public void onError(Throwable t) { */ protected final void fail(Throwable t) { Exceptions.throwIfFatal(t); - s.cancel(); + upstream.cancel(); onError(t); } @@ -116,7 +116,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } /** @@ -149,12 +149,12 @@ protected final int transitiveBoundaryFusion(int mode) { @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override diff --git a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java index 6af9b49fc1..945d311b29 100644 --- a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java @@ -29,10 +29,10 @@ public abstract class BasicFuseableSubscriber<T, R> implements FlowableSubscriber<T>, QueueSubscription<R> { /** The downstream subscriber. */ - protected final Subscriber<? super R> actual; + protected final Subscriber<? super R> downstream; /** The upstream subscription. */ - protected Subscription s; + protected Subscription upstream; /** The upstream's QueueSubscription if not null. */ protected QueueSubscription<T> qs; @@ -45,26 +45,26 @@ public abstract class BasicFuseableSubscriber<T, R> implements FlowableSubscribe /** * Construct a BasicFuseableSubscriber by wrapping the given subscriber. - * @param actual the subscriber, not null (not verified) + * @param downstream the subscriber, not null (not verified) */ - public BasicFuseableSubscriber(Subscriber<? super R> actual) { - this.actual = actual; + public BasicFuseableSubscriber(Subscriber<? super R> downstream) { + this.downstream = downstream; } // final: fixed protocol steps to support fuseable and non-fuseable upstream @SuppressWarnings("unchecked") @Override public final void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { + if (SubscriptionHelper.validate(this.upstream, s)) { - this.s = s; + this.upstream = s; if (s instanceof QueueSubscription) { this.qs = (QueueSubscription<T>)s; } if (beforeDownstream()) { - actual.onSubscribe(this); + downstream.onSubscribe(this); afterDownstream(); } @@ -98,7 +98,7 @@ public void onError(Throwable t) { return; } done = true; - actual.onError(t); + downstream.onError(t); } /** @@ -107,7 +107,7 @@ public void onError(Throwable t) { */ protected final void fail(Throwable t) { Exceptions.throwIfFatal(t); - s.cancel(); + upstream.cancel(); onError(t); } @@ -117,7 +117,7 @@ public void onComplete() { return; } done = true; - actual.onComplete(); + downstream.onComplete(); } /** @@ -150,12 +150,12 @@ protected final int transitiveBoundaryFusion(int mode) { @Override public void request(long n) { - s.request(n); + upstream.request(n); } @Override public void cancel() { - s.cancel(); + upstream.cancel(); } @Override diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java index 6ab208cc27..72a1374420 100644 --- a/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java @@ -26,7 +26,7 @@ public abstract class BlockingBaseSubscriber<T> extends CountDownLatch T value; Throwable error; - Subscription s; + Subscription upstream; volatile boolean cancelled; @@ -36,12 +36,12 @@ public BlockingBaseSubscriber() { @Override public final void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; if (!cancelled) { s.request(Long.MAX_VALUE); if (cancelled) { - this.s = SubscriptionHelper.CANCELLED; + this.upstream = SubscriptionHelper.CANCELLED; s.cancel(); } } @@ -64,8 +64,8 @@ public final T blockingGet() { BlockingHelper.verifyNonBlocking(); await(); } catch (InterruptedException ex) { - Subscription s = this.s; - this.s = SubscriptionHelper.CANCELLED; + Subscription s = this.upstream; + this.upstream = SubscriptionHelper.CANCELLED; if (s != null) { s.cancel(); } diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java index 55e8e8c0a2..57fd44623f 100644 --- a/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java @@ -26,7 +26,7 @@ public final class BlockingFirstSubscriber<T> extends BlockingBaseSubscriber<T> public void onNext(T t) { if (value == null) { value = t; - s.cancel(); + upstream.cancel(); countDown(); } } diff --git a/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java new file mode 100644 index 0000000000..5adbfeb207 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.subscribers; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; +import org.reactivestreams.Subscription; + +import java.util.concurrent.atomic.AtomicReference; + +public final class BoundedSubscriber<T> extends AtomicReference<Subscription> + implements FlowableSubscriber<T>, Subscription, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7251123623727029452L; + final Consumer<? super T> onNext; + final Consumer<? super Throwable> onError; + final Action onComplete; + final Consumer<? super Subscription> onSubscribe; + + final int bufferSize; + int consumed; + final int limit; + + public BoundedSubscriber(Consumer<? super T> onNext, Consumer<? super Throwable> onError, + Action onComplete, Consumer<? super Subscription> onSubscribe, int bufferSize) { + super(); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onSubscribe = onSubscribe; + this.bufferSize = bufferSize; + this.limit = bufferSize - (bufferSize >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + try { + onSubscribe.accept(this); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + s.cancel(); + onError(e); + } + } + } + + @Override + public void onNext(T t) { + if (!isDisposed()) { + try { + onNext.accept(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + get().request(limit); + } else { + consumed = c; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + get().cancel(); + onError(e); + } + } + } + + @Override + public void onError(Throwable t) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(new CompositeException(t, e)); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onComplete.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void dispose() { + cancel(); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void request(long n) { + get().request(n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java index 910a898214..701b4a44bc 100644 --- a/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java @@ -30,25 +30,25 @@ public abstract class DeferredScalarSubscriber<T, R> extends DeferredScalarSubsc private static final long serialVersionUID = 2984505488220891551L; /** The upstream subscription. */ - protected Subscription s; + protected Subscription upstream; /** Can indicate if there was at least on onNext call. */ protected boolean hasValue; /** * Creates a DeferredScalarSubscriber instance and wraps a downstream Subscriber. - * @param actual the downstream subscriber, not null (not verified) + * @param downstream the downstream subscriber, not null (not verified) */ - public DeferredScalarSubscriber(Subscriber<? super R> actual) { - super(actual); + public DeferredScalarSubscriber(Subscriber<? super R> downstream) { + super(downstream); } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; - actual.onSubscribe(this); + downstream.onSubscribe(this); s.request(Long.MAX_VALUE); } @@ -57,7 +57,7 @@ public void onSubscribe(Subscription s) { @Override public void onError(Throwable t) { value = null; - actual.onError(t); + downstream.onError(t); } @Override @@ -65,13 +65,13 @@ public void onComplete() { if (hasValue) { complete(value); } else { - actual.onComplete(); + downstream.onComplete(); } } @Override public void cancel() { super.cancel(); - s.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java index 812a0d601e..5e15bea285 100644 --- a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java @@ -28,7 +28,6 @@ public final class ForEachWhileSubscriber<T> extends AtomicReference<Subscription> implements FlowableSubscriber<T>, Disposable { - private static final long serialVersionUID = -4403180040475402120L; final Predicate<? super T> onNext; @@ -48,9 +47,7 @@ public ForEachWhileSubscriber(Predicate<? super T> onNext, @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); } @Override @@ -111,6 +108,6 @@ public void dispose() { @Override public boolean isDisposed() { - return SubscriptionHelper.isCancelled(this.get()); + return this.get() == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java index b2a06f53e6..4b2c329c97 100644 --- a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java @@ -24,6 +24,8 @@ import io.reactivex.internal.util.BlockingHelper; import io.reactivex.plugins.RxJavaPlugins; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + /** * A Subscriber + Future that expects exactly one upstream value and provides it * via the (blocking) Future API. @@ -36,22 +38,22 @@ public final class FutureSubscriber<T> extends CountDownLatch T value; Throwable error; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; public FutureSubscriber() { super(1); - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { for (;;) { - Subscription a = s.get(); + Subscription a = upstream.get(); if (a == this || a == SubscriptionHelper.CANCELLED) { return false; } - if (s.compareAndSet(a, SubscriptionHelper.CANCELLED)) { + if (upstream.compareAndSet(a, SubscriptionHelper.CANCELLED)) { if (a != null) { a.cancel(); } @@ -63,7 +65,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Override public boolean isCancelled() { - return SubscriptionHelper.isCancelled(s.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override @@ -93,7 +95,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution if (getCount() != 0) { BlockingHelper.verifyNonBlocking(); if (!await(timeout, unit)) { - throw new TimeoutException(); + throw new TimeoutException(timeoutMessage(timeout, unit)); } } @@ -110,15 +112,13 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(this.s, s)) { - s.request(Long.MAX_VALUE); - } + SubscriptionHelper.setOnce(this.upstream, s, Long.MAX_VALUE); } @Override public void onNext(T t) { if (value != null) { - s.get().cancel(); + upstream.get().cancel(); onError(new IndexOutOfBoundsException("More than one element received")); return; } @@ -128,13 +128,13 @@ public void onNext(T t) { @Override public void onError(Throwable t) { for (;;) { - Subscription a = s.get(); + Subscription a = upstream.get(); if (a == this || a == SubscriptionHelper.CANCELLED) { RxJavaPlugins.onError(t); return; } error = t; - if (s.compareAndSet(a, this)) { + if (upstream.compareAndSet(a, this)) { countDown(); return; } @@ -148,11 +148,11 @@ public void onComplete() { return; } for (;;) { - Subscription a = s.get(); + Subscription a = upstream.get(); if (a == this || a == SubscriptionHelper.CANCELLED) { return; } - if (s.compareAndSet(a, this)) { + if (upstream.compareAndSet(a, this)) { countDown(); return; } diff --git a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java index fc6647a83b..70ea26740e 100644 --- a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java @@ -32,7 +32,6 @@ public final class InnerQueuedSubscriber<T> extends AtomicReference<Subscription> implements FlowableSubscriber<T>, Subscription { - private static final long serialVersionUID = 22876611072430776L; final InnerQueuedSubscriberSupport<T> parent; diff --git a/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java index 0d9d0b1cf9..5568cf5156 100644 --- a/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java @@ -15,6 +15,8 @@ import java.util.concurrent.atomic.AtomicReference; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; import org.reactivestreams.Subscription; import io.reactivex.FlowableSubscriber; @@ -24,7 +26,8 @@ import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; -public final class LambdaSubscriber<T> extends AtomicReference<Subscription> implements FlowableSubscriber<T>, Subscription, Disposable { +public final class LambdaSubscriber<T> extends AtomicReference<Subscription> + implements FlowableSubscriber<T>, Subscription, Disposable, LambdaConsumerIntrospection { private static final long serialVersionUID = -7251123623727029452L; final Consumer<? super T> onNext; @@ -115,4 +118,9 @@ public void request(long n) { public void cancel() { SubscriptionHelper.cancel(this); } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } } diff --git a/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java index b23ff5957c..d07f748999 100644 --- a/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java @@ -33,7 +33,9 @@ * @param <V> the value type the child subscriber accepts */ public abstract class QueueDrainSubscriber<T, U, V> extends QueueDrainSubscriberPad4 implements FlowableSubscriber<T>, QueueDrain<U, V> { - protected final Subscriber<? super V> actual; + + protected final Subscriber<? super V> downstream; + protected final SimplePlainQueue<U> queue; protected volatile boolean cancelled; @@ -42,7 +44,7 @@ public abstract class QueueDrainSubscriber<T, U, V> extends QueueDrainSubscriber protected Throwable error; public QueueDrainSubscriber(Subscriber<? super V> actual, SimplePlainQueue<U> queue) { - this.actual = actual; + this.downstream = actual; this.queue = queue; } @@ -66,10 +68,10 @@ public final boolean fastEnter() { } protected final void fastPathEmitMax(U value, boolean delayError, Disposable dispose) { - final Subscriber<? super V> s = actual; + final Subscriber<? super V> s = downstream; final SimplePlainQueue<U> q = queue; - if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + if (fastEnter()) { long r = requested.get(); if (r != 0L) { if (accept(s, value)) { @@ -95,10 +97,10 @@ protected final void fastPathEmitMax(U value, boolean delayError, Disposable dis } protected final void fastPathOrderedEmitMax(U value, boolean delayError, Disposable dispose) { - final Subscriber<? super V> s = actual; + final Subscriber<? super V> s = downstream; final SimplePlainQueue<U> q = queue; - if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + if (fastEnter()) { long r = requested.get(); if (r != 0L) { if (q.isEmpty()) { diff --git a/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java index 8d7efc37aa..4ea2ac2368 100644 --- a/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java @@ -32,10 +32,10 @@ public abstract class SinglePostCompleteSubscriber<T, R> extends AtomicLong impl private static final long serialVersionUID = 7917814472626990048L; /** The downstream consumer. */ - protected final Subscriber<? super R> actual; + protected final Subscriber<? super R> downstream; /** The upstream subscription. */ - protected Subscription s; + protected Subscription upstream; /** The last value stored in case there is no request for it. */ protected R value; @@ -48,15 +48,15 @@ public abstract class SinglePostCompleteSubscriber<T, R> extends AtomicLong impl /** Masks out the lower 63 bit holding the current request amount. */ static final long REQUEST_MASK = Long.MAX_VALUE; - public SinglePostCompleteSubscriber(Subscriber<? super R> actual) { - this.actual = actual; + public SinglePostCompleteSubscriber(Subscriber<? super R> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -78,8 +78,8 @@ protected final void complete(R n) { } if ((r & REQUEST_MASK) != 0) { lazySet(COMPLETE_MASK + 1); - actual.onNext(n); - actual.onComplete(); + downstream.onNext(n); + downstream.onComplete(); return; } value = n; @@ -105,14 +105,14 @@ public final void request(long n) { long r = get(); if ((r & COMPLETE_MASK) != 0) { if (compareAndSet(COMPLETE_MASK, COMPLETE_MASK + 1)) { - actual.onNext(value); - actual.onComplete(); + downstream.onNext(value); + downstream.onComplete(); } break; } long u = BackpressureHelper.addCap(r, n); if (compareAndSet(r, u)) { - s.request(n); + upstream.request(n); break; } } @@ -121,6 +121,6 @@ public final void request(long n) { @Override public void cancel() { - s.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java index 6d6c7514c2..c7770bf163 100644 --- a/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java @@ -23,7 +23,7 @@ /** * Ensures that the event flow between the upstream and downstream follow - * the Reactive-Streams 1.0 specification by honoring the 3 additional rules + * the Reactive Streams 1.0 specification by honoring the 3 additional rules * (which are omitted in standard operators due to performance reasons). * <ul> * <li>§1.3: onNext should not be called concurrently until onSubscribe returns</li> @@ -41,23 +41,23 @@ public class StrictSubscriber<T> private static final long serialVersionUID = -4945028590049415624L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final AtomicThrowable error; final AtomicLong requested; - final AtomicReference<Subscription> s; + final AtomicReference<Subscription> upstream; final AtomicBoolean once; volatile boolean done; - public StrictSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + public StrictSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; this.error = new AtomicThrowable(); this.requested = new AtomicLong(); - this.s = new AtomicReference<Subscription>(); + this.upstream = new AtomicReference<Subscription>(); this.once = new AtomicBoolean(); } @@ -67,14 +67,14 @@ public void request(long n) { cancel(); onError(new IllegalArgumentException("§3.9 violated: positive request amount required but it was " + n)); } else { - SubscriptionHelper.deferredRequest(s, requested, n); + SubscriptionHelper.deferredRequest(upstream, requested, n); } } @Override public void cancel() { if (!done) { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); } } @@ -82,9 +82,9 @@ public void cancel() { public void onSubscribe(Subscription s) { if (once.compareAndSet(false, true)) { - actual.onSubscribe(this); + downstream.onSubscribe(this); - SubscriptionHelper.deferredSetOnce(this.s, requested, s); + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); } else { s.cancel(); cancel(); @@ -94,18 +94,18 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - HalfSerializer.onNext(actual, t, this, error); + HalfSerializer.onNext(downstream, t, this, error); } @Override public void onError(Throwable t) { done = true; - HalfSerializer.onError(actual, t, this, error); + HalfSerializer.onError(downstream, t, this, error); } @Override public void onComplete() { done = true; - HalfSerializer.onComplete(actual, this, error); + HalfSerializer.onComplete(downstream, this, error); } } diff --git a/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java b/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java index bb8061b610..807430bb7b 100644 --- a/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java +++ b/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java @@ -26,55 +26,55 @@ public final class SubscriberResourceWrapper<T> extends AtomicReference<Disposab private static final long serialVersionUID = -8612022020200669122L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; - final AtomicReference<Subscription> subscription = new AtomicReference<Subscription>(); + final AtomicReference<Subscription> upstream = new AtomicReference<Subscription>(); - public SubscriberResourceWrapper(Subscriber<? super T> actual) { - this.actual = actual; + public SubscriberResourceWrapper(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.setOnce(subscription, s)) { - actual.onSubscribe(this); + if (SubscriptionHelper.setOnce(upstream, s)) { + downstream.onSubscribe(this); } } @Override public void onNext(T t) { - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { DisposableHelper.dispose(this); - actual.onError(t); + downstream.onError(t); } @Override public void onComplete() { DisposableHelper.dispose(this); - actual.onComplete(); + downstream.onComplete(); } @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { - subscription.get().request(n); + upstream.get().request(n); } } @Override public void dispose() { - SubscriptionHelper.cancel(subscription); + SubscriptionHelper.cancel(upstream); DisposableHelper.dispose(this); } @Override public boolean isDisposed() { - return subscription.get() == SubscriptionHelper.CANCELLED; + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java index eea08d07e7..a3d2259fd9 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java @@ -24,7 +24,6 @@ */ public abstract class BasicIntQueueSubscription<T> extends AtomicInteger implements QueueSubscription<T> { - private static final long serialVersionUID = -6671519529404341862L; @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java index 8776e8c5a3..ebb9935ed8 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java @@ -24,7 +24,6 @@ */ public abstract class BasicQueueSubscription<T> extends AtomicLong implements QueueSubscription<T> { - private static final long serialVersionUID = -6671519529404341862L; @Override diff --git a/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java index 3e0406f4fe..131d1d2342 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java @@ -34,11 +34,10 @@ */ public class DeferredScalarSubscription<T> extends BasicIntQueueSubscription<T> { - private static final long serialVersionUID = -2151279923272604993L; /** The Subscriber to emit the value to. */ - protected final Subscriber<? super T> actual; + protected final Subscriber<? super T> downstream; /** The value is stored here if there is no request yet or in fusion mode. */ protected T value; @@ -64,10 +63,10 @@ public class DeferredScalarSubscription<T> extends BasicIntQueueSubscription<T> /** * Creates a DeferredScalarSubscription by wrapping the given Subscriber. - * @param actual the Subscriber to wrap, not null (not verified) + * @param downstream the Subscriber to wrap, not null (not verified) */ - public DeferredScalarSubscription(Subscriber<? super T> actual) { - this.actual = actual; + public DeferredScalarSubscription(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override @@ -85,7 +84,7 @@ public final void request(long n) { T v = value; if (v != null) { value = null; - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; a.onNext(v); if (get() != CANCELLED) { a.onComplete(); @@ -114,7 +113,7 @@ public final void complete(T v) { value = v; lazySet(FUSED_READY); - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; a.onNext(v); if (get() != CANCELLED) { a.onComplete(); @@ -129,7 +128,7 @@ public final void complete(T v) { if (state == HAS_REQUEST_NO_VALUE) { lazySet(HAS_REQUEST_HAS_VALUE); - Subscriber<? super T> a = actual; + Subscriber<? super T> a = downstream; a.onNext(v); if (get() != CANCELLED) { a.onComplete(); diff --git a/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java index 08fd1faae8..d4d78f3279 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java +++ b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java @@ -29,6 +29,7 @@ public enum EmptySubscription implements QueueSubscription<Object> { public void request(long n) { SubscriptionHelper.validate(n); } + @Override public void cancel() { // no-op @@ -67,27 +68,33 @@ public static void complete(Subscriber<?> s) { s.onSubscribe(INSTANCE); s.onComplete(); } + @Nullable @Override public Object poll() { return null; // always empty } + @Override public boolean isEmpty() { return true; } + @Override public void clear() { // nothing to do } + @Override public int requestFusion(int mode) { return mode & ASYNC; // accept async mode: an onComplete or onError will be signalled after anyway } + @Override public boolean offer(Object value) { throw new UnsupportedOperationException("Should not be called!"); } + @Override public boolean offer(Object v1, Object v2) { throw new UnsupportedOperationException("Should not be called!"); diff --git a/src/main/java/io/reactivex/internal/subscriptions/FullArbiter.java b/src/main/java/io/reactivex/internal/subscriptions/FullArbiter.java deleted file mode 100644 index 283ad2d2a9..0000000000 --- a/src/main/java/io/reactivex/internal/subscriptions/FullArbiter.java +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.subscriptions; - -import java.util.concurrent.atomic.*; - -import org.reactivestreams.*; - -import io.reactivex.disposables.Disposable; -import io.reactivex.internal.functions.ObjectHelper; -import io.reactivex.internal.queue.SpscLinkedArrayQueue; -import io.reactivex.internal.util.*; -import io.reactivex.plugins.RxJavaPlugins; - -/** - * Performs full arbitration of Subscriber events with strict drain (i.e., old emissions of another - * subscriber are dropped). - * - * @param <T> the value type - */ -public final class FullArbiter<T> extends FullArbiterPad2 implements Subscription { - final Subscriber<? super T> actual; - final SpscLinkedArrayQueue<Object> queue; - - long requested; - - volatile Subscription s; - static final Subscription INITIAL = new InitialSubscription(); - - - Disposable resource; - - volatile boolean cancelled; - - static final Object REQUEST = new Object(); - - public FullArbiter(Subscriber<? super T> actual, Disposable resource, int capacity) { - this.actual = actual; - this.resource = resource; - this.queue = new SpscLinkedArrayQueue<Object>(capacity); - this.s = INITIAL; - } - - @Override - public void request(long n) { - if (SubscriptionHelper.validate(n)) { - BackpressureHelper.add(missedRequested, n); - queue.offer(REQUEST, REQUEST); - drain(); - } - } - - @Override - public void cancel() { - if (!cancelled) { - cancelled = true; - dispose(); - } - } - - void dispose() { - Disposable d = resource; - resource = null; - if (d != null) { - d.dispose(); - } - } - - public boolean setSubscription(Subscription s) { - if (cancelled) { - if (s != null) { - s.cancel(); - } - return false; - } - - ObjectHelper.requireNonNull(s, "s is null"); - queue.offer(this.s, NotificationLite.subscription(s)); - drain(); - return true; - } - - public boolean onNext(T value, Subscription s) { - if (cancelled) { - return false; - } - - queue.offer(s, NotificationLite.next(value)); - drain(); - return true; - } - - public void onError(Throwable value, Subscription s) { - if (cancelled) { - RxJavaPlugins.onError(value); - return; - } - queue.offer(s, NotificationLite.error(value)); - drain(); - } - - public void onComplete(Subscription s) { - queue.offer(s, NotificationLite.complete()); - drain(); - } - - void drain() { - if (wip.getAndIncrement() != 0) { - return; - } - - int missed = 1; - - final SpscLinkedArrayQueue<Object> q = queue; - final Subscriber<? super T> a = actual; - - for (;;) { - - for (;;) { - - Object o = q.poll(); - if (o == null) { - break; - } - Object v = q.poll(); - - if (o == REQUEST) { - long mr = missedRequested.getAndSet(0L); - if (mr != 0L) { - requested = BackpressureHelper.addCap(requested, mr); - s.request(mr); - } - } else - if (o == s) { - if (NotificationLite.isSubscription(v)) { - Subscription next = NotificationLite.getSubscription(v); - if (!cancelled) { - s = next; - long r = requested; - if (r != 0L) { - next.request(r); - } - } else { - next.cancel(); - } - } else if (NotificationLite.isError(v)) { - q.clear(); - dispose(); - - Throwable ex = NotificationLite.getError(v); - if (!cancelled) { - cancelled = true; - a.onError(ex); - } else { - RxJavaPlugins.onError(ex); - } - } else if (NotificationLite.isComplete(v)) { - q.clear(); - dispose(); - - if (!cancelled) { - cancelled = true; - a.onComplete(); - } - } else { - long r = requested; - if (r != 0) { - a.onNext(NotificationLite.<T>getValue(v)); - requested = r - 1; - } - } - } - } - - missed = wip.addAndGet(-missed); - if (missed == 0) { - break; - } - } - } - - static final class InitialSubscription implements Subscription { - @Override - public void request(long n) { - // deliberately no op - } - - @Override - public void cancel() { - // deliberately no op - } - } -} - -/** Pads the object header away. */ -class FullArbiterPad0 { - volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; - volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; -} - -/** The work-in-progress counter. */ -class FullArbiterWip extends FullArbiterPad0 { - final AtomicInteger wip = new AtomicInteger(); -} - -/** Pads the wip counter away. */ -class FullArbiterPad1 extends FullArbiterWip { - volatile long p1b, p2b, p3b, p4b, p5b, p6b, p7b; - volatile long p8b, p9b, p10b, p11b, p12b, p13b, p14b, p15b; -} - -/** The missed request counter. */ -class FullArbiterMissed extends FullArbiterPad1 { - final AtomicLong missedRequested = new AtomicLong(); -} - -/** Pads the missed request counter away. */ -class FullArbiterPad2 extends FullArbiterMissed { - volatile long p1c, p2c, p3c, p4c, p5c, p6c, p7c; - volatile long p8c, p9c, p10c, p11c, p12c, p13c, p14c, p15c; -} diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java index 6abda2ce51..2796573392 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java @@ -55,11 +55,14 @@ public class SubscriptionArbiter extends AtomicInteger implements Subscription { final AtomicLong missedProduced; + final boolean cancelOnReplace; + volatile boolean cancelled; protected boolean unbounded; - public SubscriptionArbiter() { + public SubscriptionArbiter(boolean cancelOnReplace) { + this.cancelOnReplace = cancelOnReplace; missedSubscription = new AtomicReference<Subscription>(); missedRequested = new AtomicLong(); missedProduced = new AtomicLong(); @@ -80,7 +83,7 @@ public final void setSubscription(Subscription s) { if (get() == 0 && compareAndSet(0, 1)) { Subscription a = actual; - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } @@ -100,7 +103,7 @@ public final void setSubscription(Subscription s) { } Subscription a = missedSubscription.getAndSet(s); - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } drain(); @@ -240,7 +243,7 @@ final void drainLoop() { } if (ms != null) { - if (a != null) { + if (a != null && cancelOnReplace) { a.cancel(); } actual = ms; diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java index d8c8f97cea..ca19d0d4a3 100644 --- a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java @@ -92,14 +92,6 @@ public static boolean validate(long n) { public static void reportMoreProduced(long n) { RxJavaPlugins.onError(new ProtocolViolationException("More produced than requested: " + n)); } - /** - * Check if the given subscription is the common cancelled subscription. - * @param s the subscription to check - * @return true if the subscription is the common cancelled subscription - */ - public static boolean isCancelled(Subscription s) { - return s == CANCELLED; - } /** * Atomically sets the subscription on the field and cancels the @@ -137,7 +129,7 @@ public static boolean set(AtomicReference<Subscription> field, Subscription s) { * @return true if the operation succeeded, false if the target field was not null. */ public static boolean setOnce(AtomicReference<Subscription> field, Subscription s) { - ObjectHelper.requireNonNull(s, "d is null"); + ObjectHelper.requireNonNull(s, "s is null"); if (!field.compareAndSet(null, s)) { s.cancel(); if (field.get() != CANCELLED) { @@ -239,4 +231,24 @@ public static void deferredRequest(AtomicReference<Subscription> field, AtomicLo } } } + + /** + * Atomically sets the subscription on the field if it is still null and issues a positive request + * to the given {@link Subscription}. + * <p> + * If the field is not null and doesn't contain the {@link #CANCELLED} + * instance, the {@link #reportSubscriptionSet()} is called. + * @param field the target field + * @param s the new subscription to set + * @param request the amount to request, positive (not verified) + * @return true if the operation succeeded, false if the target field was not null. + * @since 2.1.11 + */ + public static boolean setOnce(AtomicReference<Subscription> field, Subscription s, long request) { + if (setOnce(field, s)) { + s.request(request); + return true; + } + return false; + } } diff --git a/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java index 13ddd34bf7..12c4d062c0 100644 --- a/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java +++ b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java @@ -91,7 +91,7 @@ public void forEachWhile(NonThrowingPredicate<? super T> consumer) { break; } if (consumer.test((T)o)) { - break; + return; } } a = (Object[])a[c]; @@ -125,7 +125,6 @@ public <U> boolean accept(Subscriber<? super U> subscriber) { return false; } - /** * Interprets the contents as NotificationLite objects and calls * the appropriate Observer method. diff --git a/src/main/java/io/reactivex/internal/util/AtomicThrowable.java b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java index 5f4d4940d8..60c19155c5 100644 --- a/src/main/java/io/reactivex/internal/util/AtomicThrowable.java +++ b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java @@ -23,7 +23,6 @@ */ public final class AtomicThrowable extends AtomicReference<Throwable> { - private static final long serialVersionUID = 3949248817947090603L; /** diff --git a/src/main/java/io/reactivex/internal/util/ExceptionHelper.java b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java index 002f2df215..ecd970c730 100644 --- a/src/main/java/io/reactivex/internal/util/ExceptionHelper.java +++ b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java @@ -14,6 +14,7 @@ package io.reactivex.internal.util; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.exceptions.CompositeException; @@ -121,6 +122,14 @@ public static <E extends Throwable> Exception throwIfThrowable(Throwable e) thro throw (E)e; } + public static String timeoutMessage(long timeout, TimeUnit unit) { + return "The source did not signal an event for " + + timeout + + " " + + unit.toString().toLowerCase() + + " and has been terminated."; + } + static final class Termination extends Throwable { private static final long serialVersionUID = -4649703670690200604L; diff --git a/src/main/java/io/reactivex/internal/util/HalfSerializer.java b/src/main/java/io/reactivex/internal/util/HalfSerializer.java index 011528ec2a..8e160ab2ea 100644 --- a/src/main/java/io/reactivex/internal/util/HalfSerializer.java +++ b/src/main/java/io/reactivex/internal/util/HalfSerializer.java @@ -74,7 +74,6 @@ public static void onError(Subscriber<?> subscriber, Throwable ex, } } - /** * Emits an onComplete signal or an onError signal with the given error or indicates * the concurrently running onNext should do that. diff --git a/src/main/java/io/reactivex/internal/util/LinkedArrayList.java b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java index a54527bdfa..6dbf3fb6e4 100644 --- a/src/main/java/io/reactivex/internal/util/LinkedArrayList.java +++ b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java @@ -87,6 +87,7 @@ public Object[] head() { public int size() { return size; } + @Override public String toString() { final int cap = capacityHint; diff --git a/src/main/java/io/reactivex/internal/util/MergerBiFunction.java b/src/main/java/io/reactivex/internal/util/MergerBiFunction.java index e3ad1d1c60..ba13a4b7a6 100644 --- a/src/main/java/io/reactivex/internal/util/MergerBiFunction.java +++ b/src/main/java/io/reactivex/internal/util/MergerBiFunction.java @@ -58,8 +58,7 @@ public List<T> apply(List<T> a, List<T> b) throws Exception { while (at.hasNext()) { both.add(at.next()); } - } else - if (s2 != null) { + } else { both.add(s2); while (bt.hasNext()) { both.add(bt.next()); diff --git a/src/main/java/io/reactivex/internal/util/NotificationLite.java b/src/main/java/io/reactivex/internal/util/NotificationLite.java index bebc0c342d..2359141e5e 100644 --- a/src/main/java/io/reactivex/internal/util/NotificationLite.java +++ b/src/main/java/io/reactivex/internal/util/NotificationLite.java @@ -64,14 +64,14 @@ public boolean equals(Object obj) { static final class SubscriptionNotification implements Serializable { private static final long serialVersionUID = -1322257508628817540L; - final Subscription s; + final Subscription upstream; SubscriptionNotification(Subscription s) { - this.s = s; + this.upstream = s; } @Override public String toString() { - return "NotificationLite.Subscription[" + s + "]"; + return "NotificationLite.Subscription[" + upstream + "]"; } } @@ -81,15 +81,15 @@ public String toString() { static final class DisposableNotification implements Serializable { private static final long serialVersionUID = -7482590109178395495L; - final Disposable d; + final Disposable upstream; DisposableNotification(Disposable d) { - this.d = d; + this.upstream = d; } @Override public String toString() { - return "NotificationLite.Disposable[" + d + "]"; + return "NotificationLite.Disposable[" + upstream + "]"; } } @@ -195,11 +195,11 @@ public static Throwable getError(Object o) { * @return the extracted Subscription */ public static Subscription getSubscription(Object o) { - return ((SubscriptionNotification)o).s; + return ((SubscriptionNotification)o).upstream; } public static Disposable getDisposable(Object o) { - return ((DisposableNotification)o).d; + return ((DisposableNotification)o).upstream; } /** @@ -230,20 +230,20 @@ public static <T> boolean accept(Object o, Subscriber<? super T> s) { * <p>Does not check for a subscription notification. * @param <T> the expected value type when unwrapped * @param o the notification object - * @param s the Observer to call methods on + * @param observer the Observer to call methods on * @return true if the notification was a terminal event (i.e., complete or error) */ @SuppressWarnings("unchecked") - public static <T> boolean accept(Object o, Observer<? super T> s) { + public static <T> boolean accept(Object o, Observer<? super T> observer) { if (o == COMPLETE) { - s.onComplete(); + observer.onComplete(); return true; } else if (o instanceof ErrorNotification) { - s.onError(((ErrorNotification)o).e); + observer.onError(((ErrorNotification)o).e); return true; } - s.onNext((T)o); + observer.onNext((T)o); return false; } @@ -266,7 +266,7 @@ public static <T> boolean acceptFull(Object o, Subscriber<? super T> s) { return true; } else if (o instanceof SubscriptionNotification) { - s.onSubscribe(((SubscriptionNotification)o).s); + s.onSubscribe(((SubscriptionNotification)o).upstream); return false; } s.onNext((T)o); @@ -277,25 +277,25 @@ public static <T> boolean acceptFull(Object o, Subscriber<? super T> s) { * Calls the appropriate Observer method based on the type of the notification. * @param <T> the expected value type when unwrapped * @param o the notification object - * @param s the subscriber to call methods on + * @param observer the subscriber to call methods on * @return true if the notification was a terminal event (i.e., complete or error) * @see #accept(Object, Observer) */ @SuppressWarnings("unchecked") - public static <T> boolean acceptFull(Object o, Observer<? super T> s) { + public static <T> boolean acceptFull(Object o, Observer<? super T> observer) { if (o == COMPLETE) { - s.onComplete(); + observer.onComplete(); return true; } else if (o instanceof ErrorNotification) { - s.onError(((ErrorNotification)o).e); + observer.onError(((ErrorNotification)o).e); return true; } else if (o instanceof DisposableNotification) { - s.onSubscribe(((DisposableNotification)o).d); + observer.onSubscribe(((DisposableNotification)o).upstream); return false; } - s.onNext((T)o); + observer.onNext((T)o); return false; } diff --git a/src/main/java/io/reactivex/internal/util/OpenHashSet.java b/src/main/java/io/reactivex/internal/util/OpenHashSet.java index 8a1b4fe50e..f971002ad0 100644 --- a/src/main/java/io/reactivex/internal/util/OpenHashSet.java +++ b/src/main/java/io/reactivex/internal/util/OpenHashSet.java @@ -140,7 +140,6 @@ void rehash() { T[] b = (T[])new Object[newCap]; - for (int j = size; j-- != 0; ) { while (a[--i] == null) { } // NOPMD int pos = mix(a[i].hashCode()) & m; diff --git a/src/main/java/io/reactivex/internal/util/Pow2.java b/src/main/java/io/reactivex/internal/util/Pow2.java index d9782a53b0..db4317132c 100644 --- a/src/main/java/io/reactivex/internal/util/Pow2.java +++ b/src/main/java/io/reactivex/internal/util/Pow2.java @@ -11,7 +11,6 @@ * the License for the specific language governing permissions and limitations under the License. */ - /* * Original License: https://github.com/JCTools/JCTools/blob/master/LICENSE * Original location: https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/util/Pow2.java diff --git a/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java b/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java index 2964812c4d..ade0d5fa82 100644 --- a/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java +++ b/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java @@ -158,7 +158,7 @@ public static <T, U> void drainLoop(SimplePlainQueue<T> q, Observer<? super U> a } public static <T, U> boolean checkTerminated(boolean d, boolean empty, - Observer<?> s, boolean delayError, SimpleQueue<?> q, Disposable disposable, ObservableQueueDrain<T, U> qd) { + Observer<?> observer, boolean delayError, SimpleQueue<?> q, Disposable disposable, ObservableQueueDrain<T, U> qd) { if (qd.cancelled()) { q.clear(); disposable.dispose(); @@ -168,12 +168,14 @@ public static <T, U> boolean checkTerminated(boolean d, boolean empty, if (d) { if (delayError) { if (empty) { - disposable.dispose(); + if (disposable != null) { + disposable.dispose(); + } Throwable err = qd.error(); if (err != null) { - s.onError(err); + observer.onError(err); } else { - s.onComplete(); + observer.onComplete(); } return true; } @@ -181,13 +183,17 @@ public static <T, U> boolean checkTerminated(boolean d, boolean empty, Throwable err = qd.error(); if (err != null) { q.clear(); - disposable.dispose(); - s.onError(err); + if (disposable != null) { + disposable.dispose(); + } + observer.onError(err); return true; } else if (empty) { - disposable.dispose(); - s.onComplete(); + if (disposable != null) { + disposable.dispose(); + } + observer.onComplete(); return true; } } diff --git a/src/main/java/io/reactivex/observables/ConnectableObservable.java b/src/main/java/io/reactivex/observables/ConnectableObservable.java index 31c3e63e9e..09fa70899e 100644 --- a/src/main/java/io/reactivex/observables/ConnectableObservable.java +++ b/src/main/java/io/reactivex/observables/ConnectableObservable.java @@ -13,15 +13,17 @@ package io.reactivex.observables; -import io.reactivex.annotations.NonNull; +import java.util.concurrent.TimeUnit; import io.reactivex.*; +import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.functions.*; import io.reactivex.internal.operators.observable.*; import io.reactivex.internal.util.ConnectConsumer; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; /** * A {@code ConnectableObservable} resembles an ordinary {@link Observable}, except that it does not begin @@ -64,21 +66,165 @@ public final Disposable connect() { return cc.disposable; } + /** + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing observers and refCount won't hang. + * + * @return the ConnectableObservable to work with + * @since 2.2.10 + */ + @SuppressWarnings("unchecked") + private ConnectableObservable<T> onRefCount() { + if (this instanceof ObservablePublishClassic) { + return RxJavaPlugins.onAssembly( + new ObservablePublishAlt<T>(((ObservablePublishClassic<T>)this).publishSource()) + ); + } + return this; + } + /** * Returns an {@code Observable} that stays connected to this {@code ConnectableObservable} as long as there * is at least one subscription to this {@code ConnectableObservable}. - * + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload does not operate on any particular {@link Scheduler}.</dd> + * </dl> * @return an {@link Observable} * @see <a href="http://reactivex.io/documentation/operators/refcount.html">ReactiveX documentation: RefCount</a> + * @see #refCount(int) + * @see #refCount(long, TimeUnit) + * @see #refCount(int, long, TimeUnit) */ @NonNull + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) public Observable<T> refCount() { - return RxJavaPlugins.onAssembly(new ObservableRefCount<T>(this)); + return RxJavaPlugins.onAssembly(new ObservableRefCount<T>(onRefCount())); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect if all subscribers have unsubscribed. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload does not operate on any particular {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable<T> refCount(int subscriberCount) { + return refCount(subscriberCount, 0, TimeUnit.NANOSECONDS, Schedulers.trampoline()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Observable instance + * @see #refCount(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable<T> refCount(long timeout, TimeUnit unit) { + return refCount(1, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the specified {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable<T> refCount(long timeout, TimeUnit unit, Scheduler scheduler) { + return refCount(1, timeout, unit, scheduler); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Observable instance + * @see #refCount(int, long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable<T> refCount(int subscriberCount, long timeout, TimeUnit unit) { + return refCount(subscriberCount, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>This {@code refCount} overload operates on the specified {@link Scheduler}.</dd> + * </dl> + * <p>History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable<T> refCount(int subscriberCount, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableRefCount<T>(onRefCount(), subscriberCount, timeout, unit, scheduler)); } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable * when the first Observer subscribes. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.o.png" alt=""> + * <p> + * The connection happens after the first subscription and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. + * <p> + * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. * * @return an Observable that automatically connects to this ConnectableObservable * when the first Observer subscribes @@ -89,8 +235,20 @@ public Observable<T> autoConnect() { } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable * when the specified number of Observers subscribe to it. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.o.png" alt=""> + * <p> + * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. + * <p> + * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. * * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates @@ -104,9 +262,17 @@ public Observable<T> autoConnect(int numberOfSubscribers) { } /** - * Returns an Observable that automatically connects to this ConnectableObservable + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable * when the specified number of Subscribers subscribe to it and calls the * specified callback with the Subscription associated with the established connection. + * <p> + * <img width="640" height="348" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/autoConnect.o.png" alt=""> + * <p> + * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. * * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates diff --git a/src/main/java/io/reactivex/observables/package-info.java b/src/main/java/io/reactivex/observables/package-info.java index d5fce46cc2..1cfff8c889 100644 --- a/src/main/java/io/reactivex/observables/package-info.java +++ b/src/main/java/io/reactivex/observables/package-info.java @@ -15,7 +15,8 @@ */ /** - * Classes supporting the Observable base reactive class: connectable and grouped - * observables. + * Classes supporting the Observable base reactive class: + * {@link io.reactivex.observables.ConnectableObservable} and + * {@link io.reactivex.observables.GroupedObservable}. */ package io.reactivex.observables; diff --git a/src/main/java/io/reactivex/observers/BaseTestConsumer.java b/src/main/java/io/reactivex/observers/BaseTestConsumer.java index a7f9bf5008..4c92c27468 100644 --- a/src/main/java/io/reactivex/observers/BaseTestConsumer.java +++ b/src/main/java/io/reactivex/observers/BaseTestConsumer.java @@ -74,6 +74,20 @@ public final Thread lastThread() { /** * Returns a shared list of received onNext values. + * <p> + * Note that accessing the items via certain methods of the {@link List} + * interface while the upstream is still actively emitting + * more items may result in a {@code ConcurrentModificationException}. + * <p> + * The {@link List#size()} method will return the number of items + * already received by this TestObserver/TestSubscriber in a thread-safe + * manner that can be read via {@link List#get(int)}) method + * (index range of 0 to {@code List.size() - 1}). + * <p> + * A view of the returned List can be created via {@link List#subList(int, int)} + * by using the bounds 0 (inclusive) to {@link List#size()} (exclusive) which, + * when accessed in a read-only fashion, should be also thread-safe and not throw any + * {@code ConcurrentModificationException}. * @return a list of received onNext values */ public final List<T> values() { @@ -82,6 +96,20 @@ public final List<T> values() { /** * Returns a shared list of received onError exceptions. + * <p> + * Note that accessing the errors via certain methods of the {@link List} + * interface while the upstream is still actively emitting + * more items or errors may result in a {@code ConcurrentModificationException}. + * <p> + * The {@link List#size()} method will return the number of errors + * already received by this TestObserver/TestSubscriber in a thread-safe + * manner that can be read via {@link List#get(int)}) method + * (index range of 0 to {@code List.size() - 1}). + * <p> + * A view of the returned List can be created via {@link List#subList(int, int)} + * by using the bounds 0 (inclusive) to {@link List#size()} (exclusive) which, + * when accessed in a read-only fashion, should be also thread-safe and not throw any + * {@code ConcurrentModificationException}. * @return a list of received events onError exceptions */ public final List<Throwable> errors() { @@ -120,7 +148,6 @@ public final int errorCount() { return errors.size(); } - /** * Fail with the given message and add the sequence of errors as suppressed ones. * <p>Note this is deliberately the only fail method. Most of the times an assertion @@ -206,7 +233,7 @@ public final boolean await(long time, TimeUnit unit) throws InterruptedException /** * Assert that this TestObserver/TestSubscriber received exactly one onComplete event. - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertComplete() { @@ -222,7 +249,7 @@ public final U assertComplete() { /** * Assert that this TestObserver/TestSubscriber has not received any onComplete event. - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertNotComplete() { @@ -238,7 +265,7 @@ public final U assertNotComplete() { /** * Assert that this TestObserver/TestSubscriber has not received any onError event. - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertNoErrors() { @@ -257,7 +284,7 @@ public final U assertNoErrors() { * overload to test against the class of an error instead of an instance of an error * or {@link #assertError(Predicate)} to test with different condition. * @param error the error to check - * @return this; + * @return this * @see #assertError(Class) * @see #assertError(Predicate) */ @@ -269,7 +296,7 @@ public final U assertError(Throwable error) { * Asserts that this TestObserver/TestSubscriber received exactly one onError event which is an * instance of the specified errorClass class. * @param errorClass the error class to expect - * @return this; + * @return this */ @SuppressWarnings({ "unchecked", "rawtypes", "cast" }) public final U assertError(Class<? extends Throwable> errorClass) { @@ -318,24 +345,24 @@ public final U assertError(Predicate<Throwable> errorPredicate) { * Assert that this TestObserver/TestSubscriber received exactly one onNext value which is equal to * the given value with respect to Objects.equals. * @param value the value to expect - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertValue(T value) { int s = values.size(); if (s != 1) { - throw fail("Expected: " + valueAndClass(value) + ", Actual: " + values); + throw fail("expected: " + valueAndClass(value) + " but was: " + values); } T v = values.get(0); if (!ObjectHelper.equals(value, v)) { - throw fail("Expected: " + valueAndClass(value) + ", Actual: " + valueAndClass(v)); + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); } return (U)this; } /** * Assert that this TestObserver/TestSubscriber did not receive an onNext value which is equal to - * the given value with respect to Objects.equals. + * the given value with respect to null-safe Object.equals. * * <p>History: 2.0.5 - experimental * @param value the value to expect not being received @@ -401,6 +428,33 @@ public final U assertNever(Predicate<? super T> valuePredicate) { return (U)this; } + /** + * Asserts that this TestObserver/TestSubscriber received an onNext value at the given index + * which is equal to the given value with respect to null-safe Object.equals. + * <p>History: 2.1.3 - experimental + * @param index the position to assert on + * @param value the value to expect + * @return this + * @since 2.2 + */ + @SuppressWarnings("unchecked") + public final U assertValueAt(int index, T value) { + int s = values.size(); + if (s == 0) { + throw fail("No values"); + } + + if (index >= s) { + throw fail("Invalid index: " + index); + } + + T v = values.get(index); + if (!ObjectHelper.equals(value, v)) { + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); + } + return (U)this; + } + /** * Asserts that this TestObserver/TestSubscriber received an onNext value at the given index * for the provided predicate returns true. @@ -452,20 +506,20 @@ public static String valueAndClass(Object o) { /** * Assert that this TestObserver/TestSubscriber received the specified number onNext events. * @param count the expected number of onNext events - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertValueCount(int count) { int s = values.size(); if (s != count) { - throw fail("Value counts differ; Expected: " + count + ", Actual: " + s); + throw fail("Value counts differ; expected: " + count + " but was: " + s); } return (U)this; } /** * Assert that this TestObserver/TestSubscriber has not received any onNext events. - * @return this; + * @return this */ public final U assertNoValues() { return assertValueCount(0); @@ -474,33 +528,52 @@ public final U assertNoValues() { /** * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order. * @param values the values expected - * @return this; + * @return this * @see #assertValueSet(Collection) */ @SuppressWarnings("unchecked") public final U assertValues(T... values) { int s = this.values.size(); if (s != values.length) { - throw fail("Value count differs; Expected: " + values.length + " " + Arrays.toString(values) - + ", Actual: " + s + " " + this.values); + throw fail("Value count differs; expected: " + values.length + " " + Arrays.toString(values) + + " but was: " + s + " " + this.values); } for (int i = 0; i < s; i++) { T v = this.values.get(i); T u = values[i]; if (!ObjectHelper.equals(u, v)) { - throw fail("Values at position " + i + " differ; Expected: " + valueAndClass(u) + ", Actual: " + valueAndClass(v)); + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); } } return (U)this; } /** - * Assert that the TestObserver/TestSubscriber received only the specified values in any order. - * <p>This helps asserting when the order of the values is not guaranteed, i.e., when merging + * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order without terminating. + * <p>History: 2.1.4 - experimental + * @param values the values expected + * @return this + * @since 2.2 + */ + public final U assertValuesOnly(T... values) { + return assertSubscribed() + .assertValues(values) + .assertNoErrors() + .assertNotComplete(); + } + + /** + * Assert that the TestObserver/TestSubscriber received only items that are in the specified + * collection as well, irrespective of the order they were received. + * <p> + * This helps asserting when the order of the values is not guaranteed, i.e., when merging * asynchronous streams. + * <p> + * To ensure that only the expected items have been received, no more and no less, in any order, + * apply {@link #assertValueCount(int)} with {@code expected.size()}. * * @param expected the collection of values expected in any order - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertValueSet(Collection<? extends T> expected) { @@ -516,31 +589,45 @@ public final U assertValueSet(Collection<? extends T> expected) { return (U)this; } + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in any order without terminating. + * <p>History: 2.1.14 - experimental + * @param expected the collection of values expected in any order + * @return this + * @since 2.2 + */ + public final U assertValueSetOnly(Collection<? extends T> expected) { + return assertSubscribed() + .assertValueSet(expected) + .assertNoErrors() + .assertNotComplete(); + } + /** * Assert that the TestObserver/TestSubscriber received only the specified sequence of values in the same order. * @param sequence the sequence of expected values in order - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertValueSequence(Iterable<? extends T> sequence) { int i = 0; - Iterator<T> vit = values.iterator(); - Iterator<? extends T> it = sequence.iterator(); + Iterator<T> actualIterator = values.iterator(); + Iterator<? extends T> expectedIterator = sequence.iterator(); boolean actualNext; boolean expectedNext; for (;;) { - actualNext = it.hasNext(); - expectedNext = vit.hasNext(); + expectedNext = expectedIterator.hasNext(); + actualNext = actualIterator.hasNext(); if (!actualNext || !expectedNext) { break; } - T v = it.next(); - T u = vit.next(); + T u = expectedIterator.next(); + T v = actualIterator.next(); if (!ObjectHelper.equals(u, v)) { - throw fail("Values at position " + i + " differ; Expected: " + valueAndClass(u) + ", Actual: " + valueAndClass(v)); + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); } i++; } @@ -554,9 +641,23 @@ public final U assertValueSequence(Iterable<? extends T> sequence) { return (U)this; } + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order without terminating. + * <p>History: 2.1.14 - experimental + * @param sequence the sequence of expected values in order + * @return this + * @since 2.2 + */ + public final U assertValueSequenceOnly(Iterable<? extends T> sequence) { + return assertSubscribed() + .assertValueSequence(sequence) + .assertNoErrors() + .assertNotComplete(); + } + /** * Assert that the TestObserver/TestSubscriber terminated (i.e., the terminal latch reached zero). - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertTerminated() { @@ -580,7 +681,7 @@ public final U assertTerminated() { /** * Assert that the TestObserver/TestSubscriber has not terminated (i.e., the terminal latch is still non-zero). - * @return this; + * @return this */ @SuppressWarnings("unchecked") public final U assertNotTerminated() { @@ -636,7 +737,7 @@ public final U assertErrorMessage(String message) { Throwable e = errors.get(0); String errorMessage = e.getMessage(); if (!ObjectHelper.equals(message, errorMessage)) { - throw fail("Error message differs; Expected: " + message + ", Actual: " + errorMessage); + throw fail("Error message differs; exptected: " + message + " but was: " + errorMessage); } } else { throw fail("Multiple errors"); @@ -670,13 +771,13 @@ public final List<List<Object>> getEvents() { /** * Assert that the onSubscribe method was called exactly once. - * @return this; + * @return this */ public abstract U assertSubscribed(); /** * Assert that the onSubscribe method hasn't been called at all. - * @return this; + * @return this */ public abstract U assertNotSubscribed(); @@ -766,7 +867,6 @@ public final U awaitDone(long time, TimeUnit unit) { return (U)this; } - /** * Assert that the TestObserver/TestSubscriber has received a Disposable but no other events. * @return this @@ -855,7 +955,6 @@ static void sleep(int millis) { } } - /** * Await until the TestObserver/TestSubscriber receives the given * number of items or terminates by sleeping 10 milliseconds at a time @@ -923,6 +1022,7 @@ public final U awaitCount(int atLeast, Runnable waitStrategy, long timeoutMillis } /** + * Returns true if an await timed out. * @return true if one of the timeout-based await methods has timed out. * <p>History: 2.0.7 - experimental * @see #clearTimeout() @@ -961,7 +1061,6 @@ public final U assertTimeout() { return (U)this; } - /** * Asserts that some awaitX method has not timed out. * <p>History: 2.0.7 - experimental diff --git a/src/main/java/io/reactivex/observers/DefaultObserver.java b/src/main/java/io/reactivex/observers/DefaultObserver.java index 7ae2f25d9a..144cea72ae 100644 --- a/src/main/java/io/reactivex/observers/DefaultObserver.java +++ b/src/main/java/io/reactivex/observers/DefaultObserver.java @@ -40,7 +40,7 @@ * * <p>Example<pre><code> * Observable.range(1, 5) - * .subscribe(new DefaultObserver<Integer>() { + * .subscribe(new DefaultObserver<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * } @@ -62,11 +62,13 @@ * @param <T> the value type */ public abstract class DefaultObserver<T> implements Observer<T> { - private Disposable s; + + private Disposable upstream; + @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.validate(this.s, s, getClass())) { - this.s = s; + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.validate(this.upstream, d, getClass())) { + this.upstream = d; onStart(); } } @@ -75,9 +77,9 @@ public final void onSubscribe(@NonNull Disposable s) { * Cancels the upstream's disposable. */ protected final void cancel() { - Disposable s = this.s; - this.s = DisposableHelper.DISPOSED; - s.dispose(); + Disposable upstream = this.upstream; + this.upstream = DisposableHelper.DISPOSED; + upstream.dispose(); } /** * Called once the subscription has been set on this observer; override this diff --git a/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java b/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java index 31a02f4831..5ec519492c 100644 --- a/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java +++ b/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java @@ -36,7 +36,7 @@ * <p>Example<pre><code> * Disposable d = * Completable.complete().delay(1, TimeUnit.SECONDS) - * .subscribeWith(new DisposableMaybeObserver<Integer>() { + * .subscribeWith(new DisposableMaybeObserver<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * } @@ -52,11 +52,12 @@ * </code></pre> */ public abstract class DisposableCompletableObserver implements CompletableObserver, Disposable { - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -69,11 +70,11 @@ protected void onStart() { @Override public final boolean isDisposed() { - return s.get() == DisposableHelper.DISPOSED; + return upstream.get() == DisposableHelper.DISPOSED; } @Override public final void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); } } diff --git a/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java b/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java index 059fe0efb7..6cff241c22 100644 --- a/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java +++ b/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java @@ -40,7 +40,7 @@ * <p>Example<pre><code> * Disposable d = * Maybe.just(1).delay(1, TimeUnit.SECONDS) - * .subscribeWith(new DisposableMaybeObserver<Integer>() { + * .subscribeWith(new DisposableMaybeObserver<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * } @@ -62,11 +62,11 @@ */ public abstract class DisposableMaybeObserver<T> implements MaybeObserver<T>, Disposable { - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -79,11 +79,11 @@ protected void onStart() { @Override public final boolean isDisposed() { - return s.get() == DisposableHelper.DISPOSED; + return upstream.get() == DisposableHelper.DISPOSED; } @Override public final void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); } } diff --git a/src/main/java/io/reactivex/observers/DisposableObserver.java b/src/main/java/io/reactivex/observers/DisposableObserver.java index f08785874b..dccd973549 100644 --- a/src/main/java/io/reactivex/observers/DisposableObserver.java +++ b/src/main/java/io/reactivex/observers/DisposableObserver.java @@ -41,7 +41,7 @@ * <p>Example<pre><code> * Disposable d = * Observable.range(1, 5) - * .subscribeWith(new DisposableObserver<Integer>() { + * .subscribeWith(new DisposableObserver<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * } @@ -66,11 +66,11 @@ */ public abstract class DisposableObserver<T> implements Observer<T>, Disposable { - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -83,11 +83,11 @@ protected void onStart() { @Override public final boolean isDisposed() { - return s.get() == DisposableHelper.DISPOSED; + return upstream.get() == DisposableHelper.DISPOSED; } @Override public final void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); } } diff --git a/src/main/java/io/reactivex/observers/DisposableSingleObserver.java b/src/main/java/io/reactivex/observers/DisposableSingleObserver.java index 84f67170a3..38224e0668 100644 --- a/src/main/java/io/reactivex/observers/DisposableSingleObserver.java +++ b/src/main/java/io/reactivex/observers/DisposableSingleObserver.java @@ -36,7 +36,7 @@ * <p>Example<pre><code> * Disposable d = * Single.just(1).delay(1, TimeUnit.SECONDS) - * .subscribeWith(new DisposableSingleObserver<Integer>() { + * .subscribeWith(new DisposableSingleObserver<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * } @@ -55,11 +55,11 @@ */ public abstract class DisposableSingleObserver<T> implements SingleObserver<T>, Disposable { - final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -72,11 +72,11 @@ protected void onStart() { @Override public final boolean isDisposed() { - return s.get() == DisposableHelper.DISPOSED; + return upstream.get() == DisposableHelper.DISPOSED; } @Override public final void dispose() { - DisposableHelper.dispose(s); + DisposableHelper.dispose(upstream); } } diff --git a/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java b/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java new file mode 100644 index 0000000000..31588ab0c9 --- /dev/null +++ b/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.observers; + +/** + * An interface that indicates that the implementing type is composed of individual components and exposes information + * about their behavior. + * + * <p><em>NOTE:</em> This is considered a read-only public API and is not intended to be implemented externally. + * <p>History: 2.1.4 - experimental + * @since 2.2 + */ +public interface LambdaConsumerIntrospection { + + /** + * Returns true or false if a custom onError consumer has been provided. + * @return {@code true} if a custom onError consumer implementation was supplied. Returns {@code false} if the + * implementation is missing an error consumer and thus using a throwing default implementation. + */ + boolean hasCustomOnError(); + +} diff --git a/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java b/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java index e9300ee977..ead4570ff9 100644 --- a/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java +++ b/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java @@ -56,7 +56,7 @@ * .subscribeWith(new ResourceCompletableObserver() { * @Override public void onStart() { * add(Schedulers.single() - * .scheduleDirect(() -> System.out.println("Time!"), + * .scheduleDirect(() -> System.out.println("Time!"), * 2, TimeUnit.SECONDS)); * } * @Override public void onError(Throwable t) { @@ -74,7 +74,7 @@ */ public abstract class ResourceCompletableObserver implements CompletableObserver, Disposable { /** The active subscription. */ - private final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + private final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -92,8 +92,8 @@ public final void add(@NonNull Disposable resource) { } @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -116,7 +116,7 @@ protected void onStart() { */ @Override public final void dispose() { - if (DisposableHelper.dispose(s)) { + if (DisposableHelper.dispose(upstream)) { resources.dispose(); } } @@ -127,6 +127,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } } diff --git a/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java b/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java index 6b97f2219e..ca603cf3b0 100644 --- a/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java +++ b/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java @@ -57,10 +57,10 @@ * <p>Example<pre><code> * Disposable d = * Maybe.just(1).delay(1, TimeUnit.SECONDS) - * .subscribeWith(new ResourceMaybeObserver<Integer>() { + * .subscribeWith(new ResourceMaybeObserver<Integer>() { * @Override public void onStart() { * add(Schedulers.single() - * .scheduleDirect(() -> System.out.println("Time!"), + * .scheduleDirect(() -> System.out.println("Time!"), * 2, TimeUnit.SECONDS)); * } * @Override public void onSuccess(Integer t) { @@ -84,7 +84,7 @@ */ public abstract class ResourceMaybeObserver<T> implements MaybeObserver<T>, Disposable { /** The active subscription. */ - private final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + private final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -102,8 +102,8 @@ public final void add(@NonNull Disposable resource) { } @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -126,7 +126,7 @@ protected void onStart() { */ @Override public final void dispose() { - if (DisposableHelper.dispose(s)) { + if (DisposableHelper.dispose(upstream)) { resources.dispose(); } } @@ -137,6 +137,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } } diff --git a/src/main/java/io/reactivex/observers/ResourceObserver.java b/src/main/java/io/reactivex/observers/ResourceObserver.java index 413cd5429d..f30d4475df 100644 --- a/src/main/java/io/reactivex/observers/ResourceObserver.java +++ b/src/main/java/io/reactivex/observers/ResourceObserver.java @@ -52,10 +52,10 @@ * <p>Example<pre><code> * Disposable d = * Observable.range(1, 5) - * .subscribeWith(new ResourceObserver<Integer>() { + * .subscribeWith(new ResourceObserver<Integer>() { * @Override public void onStart() { * add(Schedulers.single() - * .scheduleDirect(() -> System.out.println("Time!"), + * .scheduleDirect(() -> System.out.println("Time!"), * 2, TimeUnit.SECONDS)); * request(1); * } @@ -82,7 +82,7 @@ */ public abstract class ResourceObserver<T> implements Observer<T>, Disposable { /** The active subscription. */ - private final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + private final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -100,8 +100,8 @@ public final void add(@NonNull Disposable resource) { } @Override - public final void onSubscribe(Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -124,7 +124,7 @@ protected void onStart() { */ @Override public final void dispose() { - if (DisposableHelper.dispose(s)) { + if (DisposableHelper.dispose(upstream)) { resources.dispose(); } } @@ -135,6 +135,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } } diff --git a/src/main/java/io/reactivex/observers/ResourceSingleObserver.java b/src/main/java/io/reactivex/observers/ResourceSingleObserver.java index c62ebcb2db..2f533b8d99 100644 --- a/src/main/java/io/reactivex/observers/ResourceSingleObserver.java +++ b/src/main/java/io/reactivex/observers/ResourceSingleObserver.java @@ -54,10 +54,10 @@ * <p>Example<pre><code> * Disposable d = * Single.just(1).delay(1, TimeUnit.SECONDS) - * .subscribeWith(new ResourceSingleObserver<Integer>() { + * .subscribeWith(new ResourceSingleObserver<Integer>() { * @Override public void onStart() { * add(Schedulers.single() - * .scheduleDirect(() -> System.out.println("Time!"), + * .scheduleDirect(() -> System.out.println("Time!"), * 2, TimeUnit.SECONDS)); * } * @Override public void onSuccess(Integer t) { @@ -77,7 +77,7 @@ */ public abstract class ResourceSingleObserver<T> implements SingleObserver<T>, Disposable { /** The active subscription. */ - private final AtomicReference<Disposable> s = new AtomicReference<Disposable>(); + private final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -95,8 +95,8 @@ public final void add(@NonNull Disposable resource) { } @Override - public final void onSubscribe(@NonNull Disposable s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { onStart(); } } @@ -119,7 +119,7 @@ protected void onStart() { */ @Override public final void dispose() { - if (DisposableHelper.dispose(s)) { + if (DisposableHelper.dispose(upstream)) { resources.dispose(); } } @@ -130,6 +130,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return DisposableHelper.isDisposed(s.get()); + return DisposableHelper.isDisposed(upstream.get()); } } diff --git a/src/main/java/io/reactivex/observers/SafeObserver.java b/src/main/java/io/reactivex/observers/SafeObserver.java index a370f3799b..77dbfb20da 100644 --- a/src/main/java/io/reactivex/observers/SafeObserver.java +++ b/src/main/java/io/reactivex/observers/SafeObserver.java @@ -27,32 +27,32 @@ */ public final class SafeObserver<T> implements Observer<T>, Disposable { /** The actual Subscriber. */ - final Observer<? super T> actual; + final Observer<? super T> downstream; /** The subscription. */ - Disposable s; + Disposable upstream; /** Indicates a terminal state. */ boolean done; /** * Constructs a SafeObserver by wrapping the given actual Observer. - * @param actual the actual Observer to wrap, not null (not validated) + * @param downstream the actual Observer to wrap, not null (not validated) */ - public SafeObserver(@NonNull Observer<? super T> actual) { - this.actual = actual; + public SafeObserver(@NonNull Observer<? super T> downstream) { + this.downstream = downstream; } @Override - public void onSubscribe(@NonNull Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; try { - actual.onSubscribe(this); + downstream.onSubscribe(this); } catch (Throwable e) { Exceptions.throwIfFatal(e); done = true; // can't call onError because the actual's state may be corrupt at this point try { - s.dispose(); + d.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); RxJavaPlugins.onError(new CompositeException(e, e1)); @@ -63,15 +63,14 @@ public void onSubscribe(@NonNull Disposable s) { } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } @Override @@ -79,7 +78,7 @@ public void onNext(@NonNull T t) { if (done) { return; } - if (s == null) { + if (upstream == null) { onNextNoSubscription(); return; } @@ -87,7 +86,7 @@ public void onNext(@NonNull T t) { if (t == null) { Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); try { - s.dispose(); + upstream.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(ex, e1)); @@ -98,11 +97,11 @@ public void onNext(@NonNull T t) { } try { - actual.onNext(t); + downstream.onNext(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { - s.dispose(); + upstream.dispose(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(e, e1)); @@ -118,7 +117,7 @@ void onNextNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptyDisposable.INSTANCE); + downstream.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -126,7 +125,7 @@ void onNextNoSubscription() { return; } try { - actual.onError(ex); + downstream.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins @@ -142,11 +141,11 @@ public void onError(@NonNull Throwable t) { } done = true; - if (s == null) { + if (upstream == null) { Throwable npe = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptyDisposable.INSTANCE); + downstream.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -154,7 +153,7 @@ public void onError(@NonNull Throwable t) { return; } try { - actual.onError(new CompositeException(t, npe)); + downstream.onError(new CompositeException(t, npe)); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins @@ -168,7 +167,7 @@ public void onError(@NonNull Throwable t) { } try { - actual.onError(t); + downstream.onError(t); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); @@ -184,13 +183,13 @@ public void onComplete() { done = true; - if (s == null) { + if (upstream == null) { onCompleteNoSubscription(); return; } try { - actual.onComplete(); + downstream.onComplete(); } catch (Throwable e) { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(e); @@ -202,7 +201,7 @@ void onCompleteNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptyDisposable.INSTANCE); + downstream.onSubscribe(EmptyDisposable.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -210,7 +209,7 @@ void onCompleteNoSubscription() { return; } try { - actual.onError(ex); + downstream.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins diff --git a/src/main/java/io/reactivex/observers/SerializedObserver.java b/src/main/java/io/reactivex/observers/SerializedObserver.java index ec2061ba97..31badf77f8 100644 --- a/src/main/java/io/reactivex/observers/SerializedObserver.java +++ b/src/main/java/io/reactivex/observers/SerializedObserver.java @@ -31,12 +31,12 @@ * @param <T> the value type */ public final class SerializedObserver<T> implements Observer<T>, Disposable { - final Observer<? super T> actual; + final Observer<? super T> downstream; final boolean delayError; static final int QUEUE_LINK_SIZE = 4; - Disposable s; + Disposable upstream; boolean emitting; AppendOnlyLinkedArrayList<Object> queue; @@ -45,10 +45,10 @@ public final class SerializedObserver<T> implements Observer<T>, Disposable { /** * Construct a SerializedObserver by wrapping the given actual Observer. - * @param actual the actual Observer, not null (not verified) + * @param downstream the actual Observer, not null (not verified) */ - public SerializedObserver(@NonNull Observer<? super T> actual) { - this(actual, false); + public SerializedObserver(@NonNull Observer<? super T> downstream) { + this(downstream, false); } /** @@ -59,38 +59,36 @@ public SerializedObserver(@NonNull Observer<? super T> actual) { * @param delayError if true, errors are emitted after regular values have been emitted */ public SerializedObserver(@NonNull Observer<? super T> actual, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.delayError = delayError; } @Override - public void onSubscribe(@NonNull Disposable s) { - if (DisposableHelper.validate(this.s, s)) { - this.s = s; + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; - actual.onSubscribe(this); + downstream.onSubscribe(this); } } - @Override public void dispose() { - s.dispose(); + upstream.dispose(); } @Override public boolean isDisposed() { - return s.isDisposed(); + return upstream.isDisposed(); } - @Override public void onNext(@NonNull T t) { if (done) { return; } if (t == null) { - s.dispose(); + upstream.dispose(); onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); return; } @@ -110,7 +108,7 @@ public void onNext(@NonNull T t) { emitting = true; } - actual.onNext(t); + downstream.onNext(t); emitLoop(); } @@ -152,7 +150,7 @@ public void onError(@NonNull Throwable t) { return; } - actual.onError(t); + downstream.onError(t); // no need to loop because this onError is the last event } @@ -178,7 +176,7 @@ public void onComplete() { emitting = true; } - actual.onComplete(); + downstream.onComplete(); // no need to loop because this onComplete is the last event } @@ -194,7 +192,7 @@ void emitLoop() { queue = null; } - if (q.accept(actual)) { + if (q.accept(downstream)) { return; } } diff --git a/src/main/java/io/reactivex/observers/TestObserver.java b/src/main/java/io/reactivex/observers/TestObserver.java index 761414de01..3909059b27 100644 --- a/src/main/java/io/reactivex/observers/TestObserver.java +++ b/src/main/java/io/reactivex/observers/TestObserver.java @@ -18,8 +18,8 @@ import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.internal.disposables.DisposableHelper; -import io.reactivex.internal.fuseable.QueueDisposable; -import io.reactivex.internal.util.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.util.ExceptionHelper; /** * An Observer that records events and allows making assertions about them. @@ -35,12 +35,12 @@ public class TestObserver<T> extends BaseTestConsumer<T, TestObserver<T>> implements Observer<T>, Disposable, MaybeObserver<T>, SingleObserver<T>, CompletableObserver { /** The actual observer to forward events to. */ - private final Observer<? super T> actual; + private final Observer<? super T> downstream; /** Holds the current subscription if any. */ - private final AtomicReference<Disposable> subscription = new AtomicReference<Disposable>(); + private final AtomicReference<Disposable> upstream = new AtomicReference<Disposable>(); - private QueueDisposable<T> qs; + private QueueDisposable<T> qd; /** * Constructs a non-forwarding TestObserver. @@ -70,34 +70,34 @@ public TestObserver() { /** * Constructs a forwarding TestObserver. - * @param actual the actual Observer to forward events to + * @param downstream the actual Observer to forward events to */ - public TestObserver(Observer<? super T> actual) { - this.actual = actual; + public TestObserver(Observer<? super T> downstream) { + this.downstream = downstream; } @SuppressWarnings("unchecked") @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { lastThread = Thread.currentThread(); - if (s == null) { + if (d == null) { errors.add(new NullPointerException("onSubscribe received a null Subscription")); return; } - if (!subscription.compareAndSet(null, s)) { - s.dispose(); - if (subscription.get() != DisposableHelper.DISPOSED) { - errors.add(new IllegalStateException("onSubscribe received multiple subscriptions: " + s)); + if (!upstream.compareAndSet(null, d)) { + d.dispose(); + if (upstream.get() != DisposableHelper.DISPOSED) { + errors.add(new IllegalStateException("onSubscribe received multiple subscriptions: " + d)); } return; } if (initialFusionMode != 0) { - if (s instanceof QueueDisposable) { - qs = (QueueDisposable<T>)s; + if (d instanceof QueueDisposable) { + qd = (QueueDisposable<T>)d; - int m = qs.requestFusion(initialFusionMode); + int m = qd.requestFusion(initialFusionMode); establishedFusionMode = m; if (m == QueueDisposable.SYNC) { @@ -105,12 +105,12 @@ public void onSubscribe(Disposable s) { lastThread = Thread.currentThread(); try { T t; - while ((t = qs.poll()) != null) { + while ((t = qd.poll()) != null) { values.add(t); } completions++; - subscription.lazySet(DisposableHelper.DISPOSED); + upstream.lazySet(DisposableHelper.DISPOSED); } catch (Throwable ex) { // Exceptions.throwIfFatal(e); TODO add fatal exceptions? errors.add(ex); @@ -120,14 +120,14 @@ public void onSubscribe(Disposable s) { } } - actual.onSubscribe(s); + downstream.onSubscribe(d); } @Override public void onNext(T t) { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new IllegalStateException("onSubscribe not called in proper order")); } } @@ -136,13 +136,13 @@ public void onNext(T t) { if (establishedFusionMode == QueueDisposable.ASYNC) { try { - while ((t = qs.poll()) != null) { + while ((t = qd.poll()) != null) { values.add(t); } } catch (Throwable ex) { // Exceptions.throwIfFatal(e); TODO add fatal exceptions? errors.add(ex); - qs.dispose(); + qd.dispose(); } return; } @@ -153,14 +153,14 @@ public void onNext(T t) { errors.add(new NullPointerException("onNext received a null value")); } - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new IllegalStateException("onSubscribe not called in proper order")); } } @@ -173,7 +173,7 @@ public void onError(Throwable t) { errors.add(t); } - actual.onError(t); + downstream.onError(t); } finally { done.countDown(); } @@ -183,7 +183,7 @@ public void onError(Throwable t) { public void onComplete() { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new IllegalStateException("onSubscribe not called in proper order")); } } @@ -192,7 +192,7 @@ public void onComplete() { lastThread = Thread.currentThread(); completions++; - actual.onComplete(); + downstream.onComplete(); } finally { done.countDown(); } @@ -217,12 +217,12 @@ public final void cancel() { @Override public final void dispose() { - DisposableHelper.dispose(subscription); + DisposableHelper.dispose(upstream); } @Override public final boolean isDisposed() { - return DisposableHelper.isDisposed(subscription.get()); + return DisposableHelper.isDisposed(upstream.get()); } // state retrieval methods @@ -231,7 +231,7 @@ public final boolean isDisposed() { * @return true if this TestObserver received a subscription */ public final boolean hasSubscription() { - return subscription.get() != null; + return upstream.get() != null; } /** @@ -240,7 +240,7 @@ public final boolean hasSubscription() { */ @Override public final TestObserver<T> assertSubscribed() { - if (subscription.get() == null) { + if (upstream.get() == null) { throw fail("Not subscribed!"); } return this; @@ -252,7 +252,7 @@ public final TestObserver<T> assertSubscribed() { */ @Override public final TestObserver<T> assertNotSubscribed() { - if (subscription.get() != null) { + if (upstream.get() != null) { throw fail("Subscribed!"); } else if (!errors.isEmpty()) { @@ -297,7 +297,7 @@ final TestObserver<T> setInitialFusionMode(int mode) { final TestObserver<T> assertFusionMode(int mode) { int m = establishedFusionMode; if (m != mode) { - if (qs != null) { + if (qd != null) { throw new AssertionError("Fusion mode different. Expected: " + fusionModeToString(mode) + ", actual: " + fusionModeToString(m)); } else { @@ -309,9 +309,9 @@ final TestObserver<T> assertFusionMode(int mode) { static String fusionModeToString(int mode) { switch (mode) { - case QueueDisposable.NONE : return "NONE"; - case QueueDisposable.SYNC : return "SYNC"; - case QueueDisposable.ASYNC : return "ASYNC"; + case QueueFuseable.NONE : return "NONE"; + case QueueFuseable.SYNC : return "SYNC"; + case QueueFuseable.ASYNC : return "ASYNC"; default: return "Unknown(" + mode + ")"; } } @@ -323,7 +323,7 @@ static String fusionModeToString(int mode) { * @return this */ final TestObserver<T> assertFuseable() { - if (qs == null) { + if (qd == null) { throw new AssertionError("Upstream is not fuseable."); } return this; @@ -336,7 +336,7 @@ final TestObserver<T> assertFuseable() { * @return this */ final TestObserver<T> assertNotFuseable() { - if (qs != null) { + if (qd != null) { throw new AssertionError("Upstream is fuseable."); } return this; diff --git a/src/main/java/io/reactivex/observers/package-info.java b/src/main/java/io/reactivex/observers/package-info.java index cb00733554..d12329ab03 100644 --- a/src/main/java/io/reactivex/observers/package-info.java +++ b/src/main/java/io/reactivex/observers/package-info.java @@ -15,7 +15,10 @@ */ /** - * Default wrappers and implementations for Observer-based consumer classes and interfaces; - * utility classes for creating them from callbacks. + * Default wrappers and implementations for Observer-based consumer classes and interfaces, + * including disposable and resource-tracking variants and + * the {@link io.reactivex.observers.TestObserver} that allows unit testing + * {@link io.reactivex.Observable}-, {@link io.reactivex.Single}-, {@link io.reactivex.Maybe}- + * and {@link io.reactivex.Completable}-based flows. */ package io.reactivex.observers; diff --git a/src/main/java/io/reactivex/package-info.java b/src/main/java/io/reactivex/package-info.java index 5ad6fdca2f..75ceb6cd7b 100644 --- a/src/main/java/io/reactivex/package-info.java +++ b/src/main/java/io/reactivex/package-info.java @@ -14,7 +14,9 @@ * limitations under the License. */ /** - * Base reactive classes: Flowable, Observable, Single and Completable; base reactive consumers; + * Base reactive classes: {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, + * {@link io.reactivex.Single}, {@link io.reactivex.Maybe} and + * {@link io.reactivex.Completable}; base reactive consumers; * other common base interfaces. * * <p>A library that enables subscribing to and composing asynchronous events and @@ -23,7 +25,7 @@ * Completable/CompletableObserver interfaces and associated operators (in * the {@code io.reactivex.internal.operators} package) are inspired by the * Reactive Rx library in Microsoft .NET but designed and implemented on - * the more advanced Reactive-Streams ( http://www.reactivestreams.org ) principles.</p> + * the more advanced Reactive Streams ( http://www.reactivestreams.org ) principles.</p> * <p> * More information can be found at <a * href="http://msdn.microsoft.com/en-us/data/gg577609">http://msdn.microsoft.com/en-us/data/gg577609</a>. @@ -40,12 +42,12 @@ * <li>Subscriber == IAsyncEnumerator</li> * </ul> * The Single and Completable reactive base types have no equivalent in Rx.NET as of 3.x. - * </p> + * * <p>Services which intend on exposing data asynchronously and wish * to allow reactive processing and composition can implement the - * {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, {@link io.reactivex.Single} - * or {@link io.reactivex.Completable} class which then allow consumers to subscribe to them - * and receive events.</p> + * {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, {@link io.reactivex.Single}, + * {@link io.reactivex.Maybe} or {@link io.reactivex.Completable} class which then allow + * consumers to subscribe to them and receive events.</p> * <p>Usage examples can be found on the {@link io.reactivex.Flowable}/{@link io.reactivex.Observable} and {@link org.reactivestreams.Subscriber} classes.</p> */ package io.reactivex; diff --git a/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java b/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java index dd53aa622a..867c7496b5 100644 --- a/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java +++ b/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java @@ -13,14 +13,13 @@ package io.reactivex.parallel; -import io.reactivex.annotations.Experimental; import io.reactivex.functions.BiFunction; /** * Enumerations for handling failure within a parallel operator. - * @since 2.0.8 - experimental + * <p>History: 2.0.8 - experimental + * @since 2.2 */ -@Experimental public enum ParallelFailureHandling implements BiFunction<Long, Throwable, ParallelFailureHandling> { /** * The current rail is stopped and the error is dropped. diff --git a/src/main/java/io/reactivex/parallel/ParallelFlowable.java b/src/main/java/io/reactivex/parallel/ParallelFlowable.java index b1f6d60322..13ebb021a4 100644 --- a/src/main/java/io/reactivex/parallel/ParallelFlowable.java +++ b/src/main/java/io/reactivex/parallel/ParallelFlowable.java @@ -34,11 +34,10 @@ * Use {@code runOn()} to introduce where each 'rail' should run on thread-vise. * Use {@code sequential()} to merge the sources back into a single Flowable. * - * <p>History: 2.0.5 - experimental + * <p>History: 2.0.5 - experimental; 2.1 - beta * @param <T> the value type - * @since 2.1 - beta + * @since 2.2 */ -@Beta public abstract class ParallelFlowable<T> { /** @@ -122,6 +121,23 @@ public static <T> ParallelFlowable<T> from(@NonNull Publisher<? extends T> sourc return RxJavaPlugins.onAssembly(new ParallelFromPublisher<T>(source, parallelism, prefetch)); } + /** + * Calls the specified converter function during assembly time and returns its resulting value. + * <p> + * This allows fluent conversion to any other type. + * <p>History: 2.1.7 - experimental + * @param <R> the resulting object type + * @param converter the function that receives the current ParallelFlowable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final <R> R as(@NonNull ParallelFlowableConverter<T, R> converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + /** * Maps the source values on each 'rail' to another value. * <p> @@ -142,15 +158,15 @@ public final <R> ParallelFlowable<R> map(@NonNull Function<? super T, ? extends * handles errors based on the given {@link ParallelFailureHandling} enumeration value. * <p> * Note that the same mapper function may be called from multiple threads concurrently. + * <p>History: 2.0.8 - experimental * @param <R> the output value type * @param mapper the mapper function turning Ts into Us. * @param errorHandler the enumeration that defines how to handle errors thrown * from the mapper function * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental @NonNull public final <R> ParallelFlowable<R> map(@NonNull Function<? super T, ? extends R> mapper, @NonNull ParallelFailureHandling errorHandler) { ObjectHelper.requireNonNull(mapper, "mapper"); @@ -163,16 +179,16 @@ public final <R> ParallelFlowable<R> map(@NonNull Function<? super T, ? extends * handles errors based on the returned value by the handler function. * <p> * Note that the same mapper function may be called from multiple threads concurrently. + * <p>History: 2.0.8 - experimental * @param <R> the output value type * @param mapper the mapper function turning Ts into Us. * @param errorHandler the function called with the current repeat count and * failure Throwable and should return one of the {@link ParallelFailureHandling} * enumeration values to indicate how to proceed. * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental @NonNull public final <R> ParallelFlowable<R> map(@NonNull Function<? super T, ? extends R> mapper, @NonNull BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { ObjectHelper.requireNonNull(mapper, "mapper"); @@ -198,35 +214,34 @@ public final ParallelFlowable<T> filter(@NonNull Predicate<? super T> predicate) * handles errors based on the given {@link ParallelFailureHandling} enumeration value. * <p> * Note that the same predicate may be called from multiple threads concurrently. + * <p>History: 2.0.8 - experimental * @param predicate the function returning true to keep a value or false to drop a value * @param errorHandler the enumeration that defines how to handle errors thrown * from the predicate * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental public final ParallelFlowable<T> filter(@NonNull Predicate<? super T> predicate, @NonNull ParallelFailureHandling errorHandler) { ObjectHelper.requireNonNull(predicate, "predicate"); ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); return RxJavaPlugins.onAssembly(new ParallelFilterTry<T>(this, predicate, errorHandler)); } - /** * Filters the source values on each 'rail' and * handles errors based on the returned value by the handler function. * <p> * Note that the same predicate may be called from multiple threads concurrently. + * <p>History: 2.0.8 - experimental * @param predicate the function returning true to keep a value or false to drop a value * @param errorHandler the function called with the current repeat count and * failure Throwable and should return one of the {@link ParallelFailureHandling} * enumeration values to indicate how to proceed. * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental public final ParallelFlowable<T> filter(@NonNull Predicate<? super T> predicate, @NonNull BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { ObjectHelper.requireNonNull(predicate, "predicate"); ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); @@ -383,15 +398,15 @@ public final Flowable<T> sequential(int prefetch) { * <dt><b>Scheduler:</b></dt> * <dd>{@code sequentialDelayError} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> + * <p>History: 2.0.7 - experimental * @return the new Flowable instance * @see ParallelFlowable#sequentialDelayError(int) * @see ParallelFlowable#sequential() - * @since 2.0.7 - experimental + * @since 2.2 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue - @Experimental @NonNull public final Flowable<T> sequentialDelayError() { return sequentialDelayError(Flowable.bufferSize()); @@ -408,11 +423,12 @@ public final Flowable<T> sequentialDelayError() { * <dt><b>Scheduler:</b></dt> * <dd>{@code sequentialDelayError} does not operate by default on a particular {@link Scheduler}.</dd> * </dl> + * <p>History: 2.0.7 - experimental * @param prefetch the prefetch amount to use for each rail * @return the new Flowable instance * @see ParallelFlowable#sequential() * @see ParallelFlowable#sequentialDelayError() - * @since 2.0.7 - experimental + * @since 2.2 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @@ -519,19 +535,17 @@ public final ParallelFlowable<T> doOnNext(@NonNull Consumer<? super T> onNext) { )); } - /** * Call the specified consumer with the current element passing through any 'rail' and * handles errors based on the given {@link ParallelFailureHandling} enumeration value. - * + * <p>History: 2.0.8 - experimental * @param onNext the callback * @param errorHandler the enumeration that defines how to handle errors thrown * from the onNext consumer * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental @NonNull public final ParallelFlowable<T> doOnNext(@NonNull Consumer<? super T> onNext, @NonNull ParallelFailureHandling errorHandler) { ObjectHelper.requireNonNull(onNext, "onNext is null"); @@ -542,16 +556,15 @@ public final ParallelFlowable<T> doOnNext(@NonNull Consumer<? super T> onNext, @ /** * Call the specified consumer with the current element passing through any 'rail' and * handles errors based on the returned value by the handler function. - * + * <p>History: 2.0.8 - experimental * @param onNext the callback * @param errorHandler the function called with the current repeat count and * failure Throwable and should return one of the {@link ParallelFailureHandling} * enumeration values to indicate how to proceed. * @return the new ParallelFlowable instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental @NonNull public final ParallelFlowable<T> doOnNext(@NonNull Consumer<? super T> onNext, @NonNull BiFunction<? super Long, ? super Throwable, ParallelFailureHandling> errorHandler) { ObjectHelper.requireNonNull(onNext, "onNext is null"); diff --git a/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java b/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java new file mode 100644 index 0000000000..9d1b287849 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.parallel; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link ParallelFlowable#as} operator to turn a ParallelFlowable into + * another value fluently. + * <p>History: 2.1.7 - experimental + * @param <T> the upstream type + * @param <R> the output type + * @since 2.2 + */ +public interface ParallelFlowableConverter<T, R> { + /** + * Applies a function to the upstream ParallelFlowable and returns a converted value of type {@code R}. + * + * @param upstream the upstream ParallelFlowable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull ParallelFlowable<T> upstream); +} diff --git a/src/main/java/io/reactivex/parallel/ParallelTransformer.java b/src/main/java/io/reactivex/parallel/ParallelTransformer.java index f6837bf948..9981934867 100644 --- a/src/main/java/io/reactivex/parallel/ParallelTransformer.java +++ b/src/main/java/io/reactivex/parallel/ParallelTransformer.java @@ -17,13 +17,11 @@ /** * Interface to compose ParallelFlowable. - * + * <p>History: 2.0.8 - experimental * @param <Upstream> the upstream value type * @param <Downstream> the downstream value type - * - * @since 2.0.8 - experimental + * @since 2.2 */ -@Experimental public interface ParallelTransformer<Upstream, Downstream> { /** * Applies a function to the upstream ParallelFlowable and returns a ParallelFlowable with diff --git a/src/main/java/io/reactivex/parallel/package-info.java b/src/main/java/io/reactivex/parallel/package-info.java index 3e4105ea51..e7ee2576eb 100644 --- a/src/main/java/io/reactivex/parallel/package-info.java +++ b/src/main/java/io/reactivex/parallel/package-info.java @@ -15,7 +15,7 @@ */ /** - * Base type for the parallel type offering a sub-DSL for working with Flowable items - * in parallel. + * Contains the base type {@link io.reactivex.parallel.ParallelFlowable}, + * a sub-DSL for working with {@link io.reactivex.Flowable} sequences in parallel. */ package io.reactivex.parallel; \ No newline at end of file diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 5ab0192342..1d3a810cc5 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -86,6 +86,7 @@ public final class RxJavaPlugins { @Nullable static volatile Function<? super Single, ? extends Single> onSingleAssembly; + @Nullable static volatile Function<? super Completable, ? extends Completable> onCompletableAssembly; @SuppressWarnings("rawtypes") @@ -337,7 +338,24 @@ public static Scheduler onComputationScheduler(@NonNull Scheduler defaultSchedul /** * Called when an undeliverable error occurs. + * <p> + * Undeliverable errors are those {@code Observer.onError()} invocations that are not allowed to happen on + * the given consumer type ({@code Observer}, {@code Subscriber}, etc.) due to protocol restrictions + * because the consumer has either disposed/cancelled its {@code Disposable}/{@code Subscription} or + * has already terminated with an {@code onError()} or {@code onComplete()} signal. + * <p> + * By default, this global error handler prints the stacktrace via {@link Throwable#printStackTrace()} + * and calls {@link java.lang.Thread.UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} + * on the current thread. + * <p> + * Note that on some platforms, the platform runtime terminates the current application with an error if such + * uncaught exceptions happen. In this case, it is recommended the application installs a global error + * handler via the {@link #setErrorHandler(Consumer)} plugin method. + * * @param error the error to report + * @see #getErrorHandler() + * @see #setErrorHandler(Consumer) + * @see <a href="https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling">Error handling Wiki</a> */ public static void onError(@NonNull Throwable error) { Consumer<? super Throwable> f = errorHandler; @@ -446,6 +464,8 @@ public static Scheduler onNewThreadScheduler(@NonNull Scheduler defaultScheduler */ @NonNull public static Runnable onSchedule(@NonNull Runnable run) { + ObjectHelper.requireNonNull(run, "run is null"); + Function<? super Runnable, ? extends Runnable> f = onScheduleHandler; if (f == null) { return run; @@ -959,17 +979,17 @@ public static CompletableObserver onSubscribe(@NonNull Completable source, @NonN * Calls the associated hook function. * @param <T> the value type * @param source the hook's input value - * @param subscriber the subscriber + * @param observer the subscriber * @return the value returned by the hook */ @SuppressWarnings({ "rawtypes", "unchecked" }) @NonNull - public static <T> MaybeObserver<? super T> onSubscribe(@NonNull Maybe<T> source, @NonNull MaybeObserver<? super T> subscriber) { + public static <T> MaybeObserver<? super T> onSubscribe(@NonNull Maybe<T> source, @NonNull MaybeObserver<? super T> observer) { BiFunction<? super Maybe, ? super MaybeObserver, ? extends MaybeObserver> f = onMaybeSubscribe; if (f != null) { - return apply(f, source, subscriber); + return apply(f, source, observer); } - return subscriber; + return observer; } /** @@ -1084,11 +1104,10 @@ public static Completable onAssembly(@NonNull Completable source) { /** * Sets the specific hook function. - * <p>History: 2.0.6 - experimental + * <p>History: 2.0.6 - experimental; 2.1 - beta * @param handler the hook function to set, null allowed - * @since 2.1 - beta + * @since 2.2 */ - @Beta @SuppressWarnings("rawtypes") public static void setOnParallelAssembly(@Nullable Function<? super ParallelFlowable, ? extends ParallelFlowable> handler) { if (lockdown) { @@ -1099,11 +1118,10 @@ public static void setOnParallelAssembly(@Nullable Function<? super ParallelFlow /** * Returns the current hook function. - * <p>History: 2.0.6 - experimental + * <p>History: 2.0.6 - experimental; 2.1 - beta * @return the hook function, may be null - * @since 2.1 - beta + * @since 2.2 */ - @Beta @SuppressWarnings("rawtypes") @Nullable public static Function<? super ParallelFlowable, ? extends ParallelFlowable> getOnParallelAssembly() { @@ -1112,13 +1130,12 @@ public static void setOnParallelAssembly(@Nullable Function<? super ParallelFlow /** * Calls the associated hook function. - * <p>History: 2.0.6 - experimental + * <p>History: 2.0.6 - experimental; 2.1 - beta * @param <T> the value type of the source * @param source the hook's input value * @return the value returned by the hook - * @since 2.1 - beta + * @since 2.2 */ - @Beta @SuppressWarnings({ "rawtypes", "unchecked" }) @NonNull public static <T> ParallelFlowable<T> onAssembly(@NonNull ParallelFlowable<T> source) { diff --git a/src/main/java/io/reactivex/plugins/package-info.java b/src/main/java/io/reactivex/plugins/package-info.java index ad90c82534..3031129387 100644 --- a/src/main/java/io/reactivex/plugins/package-info.java +++ b/src/main/java/io/reactivex/plugins/package-info.java @@ -15,7 +15,7 @@ */ /** - * Callback types and a central plugin handler class to hook into the lifecycle - * of the base reactive types and schedulers. + * Contains the central plugin handler {@link io.reactivex.plugins.RxJavaPlugins} + * class to hook into the lifecycle of the base reactive types and schedulers. */ package io.reactivex.plugins; diff --git a/src/main/java/io/reactivex/processors/AsyncProcessor.java b/src/main/java/io/reactivex/processors/AsyncProcessor.java index 4aa6ec549c..e360d17eb3 100644 --- a/src/main/java/io/reactivex/processors/AsyncProcessor.java +++ b/src/main/java/io/reactivex/processors/AsyncProcessor.java @@ -15,18 +15,103 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import org.reactivestreams.*; + import io.reactivex.annotations.*; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.DeferredScalarSubscription; import io.reactivex.plugins.RxJavaPlugins; -import org.reactivestreams.*; /** * Processor that emits the very last value followed by a completion event or the received error * to {@link Subscriber}s. + * <p> + * <img width="640" height="239" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/AsyncProcessor.png" alt=""> + * <p> + * This processor does not have a public constructor by design; a new empty instance of this + * {@code AsyncProcessor} can be created via the {@link #create()} method. + * <p> + * Since an {@code AsyncProcessor} is a Reactive Streams {@code Processor} type, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * as parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + * <p> + * {@code AsyncProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor} and supports backpressure from the downstream but + * its {@link Subscriber}-side consumes items in an unbounded manner. + * <p> + * When this {@code AsyncProcessor} is terminated via {@link #onError(Throwable)}, the + * last observed item (if any) is cleared and late {@link Subscriber}s only receive + * the {@code onError} event. + * <p> + * The {@code AsyncProcessor} caches the latest item internally and it emits this item only when {@code onComplete} is called. + * Therefore, it is not recommended to use this {@code Processor} with infinite or never-completing sources. + * <p> + * Even though {@code AsyncProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code AsyncProcessor} reached its terminal state will result in the + * given {@link Subscription} being canceled immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * The implementation of {@code onXXX} methods are technically thread-safe but non-serialized calls + * to them may lead to undefined state in the currently subscribed {@code Subscriber}s. + * <p> + * This {@code AsyncProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the very last observed value - + * after this {@code AsyncProcessor} has been completed - in a non-blocking and thread-safe + * manner via {@link #hasValue()}, {@link #getValue()}, {@link #getValues()} or {@link #getValues(Object[])}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The {@code AsyncProcessor} honors the backpressure of the downstream {@code Subscriber}s and won't emit + * its single value to a particular {@code Subscriber} until that {@code Subscriber} has requested an item. + * When the {@code AsyncProcessor} is subscribed to a {@link io.reactivex.Flowable}, the processor consumes this + * {@code Flowable} in an unbounded manner (requesting `Long.MAX_VALUE`) as only the very last upstream item is + * retained by it. + * </dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code AsyncProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code AsyncProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s dispose their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code AsyncProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * AsyncProcessor<Object> processor = AsyncProcessor.create(); + * + * TestSubscriber<Object> ts1 = processor.test(); + * + * ts1.assertEmpty(); + * + * processor.onNext(1); * - * <p>The implementation of onXXX methods are technically thread-safe but non-serialized calls - * to them may lead to undefined state in the currently subscribed Subscribers. + * // AsyncProcessor only emits when onComplete was called. + * ts1.assertEmpty(); * + * processor.onNext(2); + * processor.onComplete(); + * + * // onComplete triggers the emission of the last cached item and the onComplete event. + * ts1.assertResult(2); + * + * TestSubscriber<Object> ts2 = processor.test(); + * + * // late Subscribers receive the last cached item too + * ts2.assertResult(2); + * </code></pre> * @param <T> the value type */ public final class AsyncProcessor<T> extends FlowableProcessor<T> { @@ -71,38 +156,23 @@ public void onSubscribe(Subscription s) { s.cancel(); return; } - // PublishSubject doesn't bother with request coordination. + // AsyncProcessor doesn't bother with request coordination. s.request(Long.MAX_VALUE); } @Override public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { return; } - if (t == null) { - nullOnNext(); - return; - } value = t; } - @SuppressWarnings("unchecked") - void nullOnNext() { - value = null; - Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - error = ex; - for (AsyncSubscription<T> as : subscribers.getAndSet(TERMINATED)) { - as.onError(ex); - } - } - @SuppressWarnings("unchecked") @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { RxJavaPlugins.onError(t); return; @@ -149,6 +219,7 @@ public boolean hasComplete() { } @Override + @Nullable public Throwable getThrowable() { return subscribers.get() == TERMINATED ? error : null; } @@ -178,9 +249,9 @@ protected void subscribeActual(Subscriber<? super T> s) { /** * Tries to add the given subscriber to the subscribers array atomically - * or returns false if the subject has terminated. + * or returns false if the processor has terminated. * @param ps the subscriber to add - * @return true if successful, false if the subject has terminated + * @return true if successful, false if the processor has terminated */ boolean add(AsyncSubscription<T> ps) { for (;;) { @@ -202,8 +273,8 @@ boolean add(AsyncSubscription<T> ps) { } /** - * Atomically removes the given subscriber if it is subscribed to the subject. - * @param ps the subject to remove + * Atomically removes the given subscriber if it is subscribed to this processor. + * @param ps the subscriber's subscription wrapper to remove */ @SuppressWarnings("unchecked") void remove(AsyncSubscription<T> ps) { @@ -242,41 +313,46 @@ void remove(AsyncSubscription<T> ps) { } /** - * Returns true if the subject has any value. + * Returns true if this processor has any value. * <p>The method is thread-safe. - * @return true if the subject has any value + * @return true if this processor has any value */ public boolean hasValue() { return subscribers.get() == TERMINATED && value != null; } /** - * Returns a single value the Subject currently has or null if no such value exists. + * Returns a single value this processor currently has or null if no such value exists. * <p>The method is thread-safe. - * @return a single value the Subject currently has or null if no such value exists + * @return a single value this processor currently has or null if no such value exists */ + @Nullable public T getValue() { return subscribers.get() == TERMINATED ? value : null; } /** - * Returns an Object array containing snapshot all values of the Subject. + * Returns an Object array containing snapshot all values of this processor. * <p>The method is thread-safe. - * @return the array containing the snapshot of all values of the Subject + * @return the array containing the snapshot of all values of this processor + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public Object[] getValues() { T v = getValue(); return v != null ? new Object[] { v } : new Object[0]; } /** - * Returns a typed array containing a snapshot of all values of the Subject. + * Returns a typed array containing a snapshot of all values of this processor. * <p>The method follows the conventions of Collection.toArray by setting the array element * after the last value to null (if the capacity permits). * <p>The method is thread-safe. * @param array the target array to copy values into if it fits * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public T[] getValues(T[] array) { T v = getValue(); if (v == null) { @@ -314,7 +390,7 @@ public void cancel() { void onComplete() { if (!isCancelled()) { - actual.onComplete(); + downstream.onComplete(); } } @@ -322,7 +398,7 @@ void onError(Throwable t) { if (isCancelled()) { RxJavaPlugins.onError(t); } else { - actual.onError(t); + downstream.onError(t); } } } diff --git a/src/main/java/io/reactivex/processors/BehaviorProcessor.java b/src/main/java/io/reactivex/processors/BehaviorProcessor.java index d033d636c0..a81c51085e 100644 --- a/src/main/java/io/reactivex/processors/BehaviorProcessor.java +++ b/src/main/java/io/reactivex/processors/BehaviorProcessor.java @@ -33,38 +33,130 @@ * <p> * <img width="640" height="460" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.BehaviorProcessor.png" alt=""> * <p> - * Example usage: + * This processor does not have a public constructor by design; a new empty instance of this + * {@code BehaviorProcessor} can be created via the {@link #create()} method and + * a new non-empty instance can be created via {@link #createDefault(Object)} (named as such to avoid + * overload resolution conflict with {@code Flowable.create} that creates a Flowable, not a {@code BehaviorProcessor}). + * <p> + * In accordance with the Reactive Streams specification (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * {@code null}s are not allowed as default initial values in {@link #createDefault(Object)} or as parameters to {@link #onNext(Object)} and + * {@link #onError(Throwable)}. + * <p> + * When this {@code BehaviorProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, the + * last observed item (if any) is cleared and late {@link org.reactivestreams.Subscriber}s only receive + * the respective terminal event. + * <p> + * The {@code BehaviorProcessor} does not support clearing its cached value (to appear empty again), however, the + * effect can be achieved by using a special item and making sure {@code Subscriber}s subscribe through a + * filter whose predicate filters out this special item: + * <pre><code> + * BehaviorProcessor<Integer> processor = BehaviorProcessor.create(); + * + * final Integer EMPTY = Integer.MIN_VALUE; + * + * Flowable<Integer> flowable = processor.filter(v -> v != EMPTY); + * + * TestSubscriber<Integer> ts1 = flowable.test(); + * + * processor.onNext(1); + * // this will "clear" the cache + * processor.onNext(EMPTY); + * + * TestSubscriber<Integer> ts2 = flowable.test(); + * + * processor.onNext(2); + * processor.onComplete(); + * + * // ts1 received both non-empty items + * ts1.assertResult(1, 2); + * + * // ts2 received only 2 even though the current item was EMPTY + * // when it got subscribed + * ts2.assertResult(2); + * + * // Subscribers coming after the processor was terminated receive + * // no items and only the onComplete event in this case. + * flowable.test().assertResult(); + * </code></pre> + * <p> + * Even though {@code BehaviorProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code BehaviorProcessor} reached its terminal state will result in the + * given {@code Subscription} being cancelled immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * Note that serializing over {@link #offer(Object)} is not supported through {@code toSerialized()} because it is a method + * available on the {@code PublishProcessor} and {@code BehaviorProcessor} classes only. + * <p> + * This {@code BehaviorProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the latest observed value + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + * <p> + * Note that this processor signals {@code MissingBackpressureException} if a particular {@code Subscriber} is not + * ready to receive {@code onNext} events. To avoid this exception being signaled, use {@link #offer(Object)} to only + * try to emit an item when all {@code Subscriber}s have requested item(s). + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The {@code BehaviorProcessor} does not coordinate requests of its downstream {@code Subscriber}s and + * expects each individual {@code Subscriber} is ready to receive {@code onNext} items when {@link #onNext(Object)} + * is called. If a {@code Subscriber} is not ready, a {@code MissingBackpressureException} is signalled to it. + * To avoid overflowing the current {@code Subscriber}s, the conditional {@link #offer(Object)} method is available + * that returns true if any of the {@code Subscriber}s is not ready to receive {@code onNext} events. If + * there are no {@code Subscriber}s to the processor, {@code offer()} always succeeds. + * If the {@code BehaviorProcessor} is (optionally) subscribed to another {@code Publisher}, this upstream + * {@code Publisher} is consumed in an unbounded fashion (requesting {@code Long.MAX_VALUE}).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code BehaviorProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code BehaviorProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code BehaviorProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> * <p> + * Example usage: * <pre> {@code - // observer will receive all events. + // subscriber will receive all events. BehaviorProcessor<Object> processor = BehaviorProcessor.create("default"); - processor.subscribe(observer); + processor.subscribe(subscriber); processor.onNext("one"); processor.onNext("two"); processor.onNext("three"); - // observer will receive the "one", "two" and "three" events, but not "zero" + // subscriber will receive the "one", "two" and "three" events, but not "zero" BehaviorProcessor<Object> processor = BehaviorProcessor.create("default"); processor.onNext("zero"); processor.onNext("one"); - processor.subscribe(observer); + processor.subscribe(subscriber); processor.onNext("two"); processor.onNext("three"); - // observer will receive only onComplete + // subscriber will receive only onComplete BehaviorProcessor<Object> processor = BehaviorProcessor.create("default"); processor.onNext("zero"); processor.onNext("one"); processor.onComplete(); - processor.subscribe(observer); + processor.subscribe(subscriber); - // observer will receive only onError + // subscriber will receive only onError BehaviorProcessor<Object> processor = BehaviorProcessor.create("default"); processor.onNext("zero"); processor.onNext("one"); processor.onError(new RuntimeException("error")); - processor.subscribe(observer); + processor.subscribe(subscriber); } </pre> * * @param <T> @@ -95,10 +187,11 @@ public final class BehaviorProcessor<T> extends FlowableProcessor<T> { * Creates a {@link BehaviorProcessor} without a default item. * * @param <T> - * the type of item the Subject will emit + * the type of item the BehaviorProcessor will emit * @return the constructed {@link BehaviorProcessor} */ @CheckReturnValue + @NonNull public static <T> BehaviorProcessor<T> create() { return new BehaviorProcessor<T>(); } @@ -108,13 +201,14 @@ public static <T> BehaviorProcessor<T> create() { * {@link Subscriber} that subscribes to it. * * @param <T> - * the type of item the Subject will emit + * the type of item the BehaviorProcessor will emit * @param defaultValue * the item that will be emitted first to any {@link Subscriber} as long as the * {@link BehaviorProcessor} has not yet observed any items from its source {@code Observable} * @return the constructed {@link BehaviorProcessor} */ @CheckReturnValue + @NonNull public static <T> BehaviorProcessor<T> createDefault(T defaultValue) { ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"); return new BehaviorProcessor<T>(defaultValue); @@ -176,10 +270,8 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (terminalEvent.get() != null) { return; } @@ -192,9 +284,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (!terminalEvent.compareAndSet(null, t)) { RxJavaPlugins.onError(t); return; @@ -225,11 +315,11 @@ public void onComplete() { * <p> * Calling with null will terminate the PublishProcessor and a NullPointerException * is signalled to the Subscribers. + * <p>History: 2.0.8 - experimental * @param t the item to emit, not null * @return true if the item was emitted to all Subscribers - * @since 2.0.8 - experimental + * @since 2.2 */ - @Experimental public boolean offer(T t) { if (t == null) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); @@ -256,12 +346,12 @@ public boolean hasSubscribers() { return subscribers.get().length != 0; } - /* test support*/ int subscriberCount() { return subscribers.get().length; } @Override + @Nullable public Throwable getThrowable() { Object o = value.get(); if (NotificationLite.isError(o)) { @@ -271,10 +361,11 @@ public Throwable getThrowable() { } /** - * Returns a single value the Subject currently has or null if no such value exists. + * Returns a single value the BehaviorProcessor currently has or null if no such value exists. * <p>The method is thread-safe. - * @return a single value the Subject currently has or null if no such value exists + * @return a single value the BehaviorProcessor currently has or null if no such value exists */ + @Nullable public T getValue() { Object o = value.get(); if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { @@ -284,10 +375,12 @@ public T getValue() { } /** - * Returns an Object array containing snapshot all values of the Subject. + * Returns an Object array containing snapshot all values of the BehaviorProcessor. * <p>The method is thread-safe. - * @return the array containing the snapshot of all values of the Subject + * @return the array containing the snapshot of all values of the BehaviorProcessor + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public Object[] getValues() { @SuppressWarnings("unchecked") T[] a = (T[])EMPTY_ARRAY; @@ -300,13 +393,15 @@ public Object[] getValues() { } /** - * Returns a typed array containing a snapshot of all values of the Subject. + * Returns a typed array containing a snapshot of all values of the BehaviorProcessor. * <p>The method follows the conventions of Collection.toArray by setting the array element * after the last value to null (if the capacity permits). * <p>The method is thread-safe. * @param array the target array to copy values into if it fits * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated @SuppressWarnings("unchecked") public T[] getValues(T[] array) { Object o = value.get(); @@ -342,16 +437,15 @@ public boolean hasThrowable() { } /** - * Returns true if the subject has any value. + * Returns true if the BehaviorProcessor has any value. * <p>The method is thread-safe. - * @return true if the subject has any value + * @return true if the BehaviorProcessor has any value */ public boolean hasValue() { Object o = value.get(); return o != null && !NotificationLite.isComplete(o) && !NotificationLite.isError(o); } - boolean add(BehaviorSubscription<T> rs) { for (;;) { BehaviorSubscription<T>[] a = subscribers.get(); @@ -373,10 +467,10 @@ boolean add(BehaviorSubscription<T> rs) { void remove(BehaviorSubscription<T> rs) { for (;;) { BehaviorSubscription<T>[] a = subscribers.get(); - if (a == TERMINATED || a == EMPTY) { + int len = a.length; + if (len == 0) { return; } - int len = a.length; int j = -1; for (int i = 0; i < len; i++) { if (a[i] == rs) { @@ -429,7 +523,7 @@ static final class BehaviorSubscription<T> extends AtomicLong implements Subscri private static final long serialVersionUID = 3293175281126227086L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final BehaviorProcessor<T> state; boolean next; @@ -443,7 +537,7 @@ static final class BehaviorSubscription<T> extends AtomicLong implements Subscri long index; BehaviorSubscription(Subscriber<? super T> actual, BehaviorProcessor<T> state) { - this.actual = actual; + this.downstream = actual; this.state = state; } @@ -533,24 +627,24 @@ public boolean test(Object o) { } if (NotificationLite.isComplete(o)) { - actual.onComplete(); + downstream.onComplete(); return true; } else if (NotificationLite.isError(o)) { - actual.onError(NotificationLite.getError(o)); + downstream.onError(NotificationLite.getError(o)); return true; } long r = get(); if (r != 0L) { - actual.onNext(NotificationLite.<T>getValue(o)); + downstream.onNext(NotificationLite.<T>getValue(o)); if (r != Long.MAX_VALUE) { decrementAndGet(); } return false; } cancel(); - actual.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); return true; } diff --git a/src/main/java/io/reactivex/processors/FlowableProcessor.java b/src/main/java/io/reactivex/processors/FlowableProcessor.java index e5fe4ad838..8135afa784 100644 --- a/src/main/java/io/reactivex/processors/FlowableProcessor.java +++ b/src/main/java/io/reactivex/processors/FlowableProcessor.java @@ -13,10 +13,11 @@ package io.reactivex.processors; -import io.reactivex.*; -import io.reactivex.annotations.NonNull; import org.reactivestreams.Processor; +import io.reactivex.*; +import io.reactivex.annotations.*; + /** * Represents a Subscriber and a Flowable (Publisher) at the same time, allowing * multicasting events from a single source to multiple child Subscribers. @@ -28,45 +29,47 @@ public abstract class FlowableProcessor<T> extends Flowable<T> implements Processor<T, T>, FlowableSubscriber<T> { /** - * Returns true if the subject has subscribers. + * Returns true if the FlowableProcessor has subscribers. * <p>The method is thread-safe. - * @return true if the subject has subscribers + * @return true if the FlowableProcessor has subscribers */ public abstract boolean hasSubscribers(); /** - * Returns true if the subject has reached a terminal state through an error event. + * Returns true if the FlowableProcessor has reached a terminal state through an error event. * <p>The method is thread-safe. - * @return true if the subject has reached a terminal state through an error event + * @return true if the FlowableProcessor has reached a terminal state through an error event * @see #getThrowable() * @see #hasComplete() */ public abstract boolean hasThrowable(); /** - * Returns true if the subject has reached a terminal state through a complete event. + * Returns true if the FlowableProcessor has reached a terminal state through a complete event. * <p>The method is thread-safe. - * @return true if the subject has reached a terminal state through a complete event + * @return true if the FlowableProcessor has reached a terminal state through a complete event * @see #hasThrowable() */ public abstract boolean hasComplete(); /** - * Returns the error that caused the Subject to terminate or null if the Subject + * Returns the error that caused the FlowableProcessor to terminate or null if the FlowableProcessor * hasn't terminated yet. * <p>The method is thread-safe. - * @return the error that caused the Subject to terminate or null if the Subject + * @return the error that caused the FlowableProcessor to terminate or null if the FlowableProcessor * hasn't terminated yet */ + @Nullable public abstract Throwable getThrowable(); /** - * Wraps this Subject and serializes the calls to the onSubscribe, onNext, onError and + * Wraps this FlowableProcessor and serializes the calls to the onSubscribe, onNext, onError and * onComplete methods, making them thread-safe. * <p>The method is thread-safe. - * @return the wrapped and serialized subject + * @return the wrapped and serialized FlowableProcessor */ @NonNull + @CheckReturnValue public final FlowableProcessor<T> toSerialized() { if (this instanceof SerializedProcessor) { return this; diff --git a/src/main/java/io/reactivex/processors/MulticastProcessor.java b/src/main/java/io/reactivex/processors/MulticastProcessor.java new file mode 100644 index 0000000000..31b7a64fea --- /dev/null +++ b/src/main/java/io/reactivex/processors/MulticastProcessor.java @@ -0,0 +1,641 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.processors; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A {@link FlowableProcessor} implementation that coordinates downstream requests through + * a front-buffer and stable-prefetching, optionally canceling the upstream if all + * subscribers have cancelled. + * <p> + * <img width="640" height="360" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/MulticastProcessor.png" alt=""> + * <p> + * This processor does not have a public constructor by design; a new empty instance of this + * {@code MulticastProcessor} can be created via the following {@code create} methods that + * allow configuring it: + * <ul> + * <li>{@link #create()}: create an empty {@code MulticastProcessor} with + * {@link io.reactivex.Flowable#bufferSize() Flowable.bufferSize()} prefetch amount + * and no reference counting behavior.</li> + * <li>{@link #create(int)}: create an empty {@code MulticastProcessor} with + * the given prefetch amount and no reference counting behavior.</li> + * <li>{@link #create(boolean)}: create an empty {@code MulticastProcessor} with + * {@link io.reactivex.Flowable#bufferSize() Flowable.bufferSize()} prefetch amount + * and an optional reference counting behavior.</li> + * <li>{@link #create(int, boolean)}: create an empty {@code MulticastProcessor} with + * the given prefetch amount and an optional reference counting behavior.</li> + * </ul> + * <p> + * When the reference counting behavior is enabled, the {@code MulticastProcessor} cancels its + * upstream when all {@link Subscriber}s have cancelled. Late {@code Subscriber}s will then be + * immediately completed. + * <p> + * Because {@code MulticastProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is mandatory (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>). + * If {@code MulticastProcessor} should run standalone, i.e., without subscribing the {@code MulticastProcessor} to another {@link Publisher}, + * use {@link #start()} or {@link #startUnbounded()} methods to initialize the internal buffer. + * Failing to do so will lead to a {@link NullPointerException} at runtime. + * <p> + * Use {@link #offer(Object)} to try and offer/emit items but don't fail if the + * internal buffer is full. + * <p> + * A {@code MulticastProcessor} is a {@link Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onSubscribe(Subscription)}, {@link #offer(Object)}, {@link #onNext(Object)} and {@link #onError(Throwable)}. + * Such calls will result in a {@link NullPointerException} being thrown and the processor's state is not changed. + * <p> + * Since a {@code MulticastProcessor} is a {@link io.reactivex.Flowable}, it supports backpressure. + * The backpressure from the currently subscribed {@link Subscriber}s are coordinated by emitting upstream + * items only if all of those {@code Subscriber}s have requested at least one item. This behavior + * is also called <em>lockstep-mode</em> because even if some {@code Subscriber}s can take any number + * of items, other {@code Subscriber}s requesting less or infrequently will slow down the overall + * throughput of the flow. + * <p> + * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * <p> + * This {@code MulticastProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. This processor doesn't allow peeking into its buffer. + * <p> + * When this {@code MulticastProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * all previously signaled but not yet consumed items will be still available to {@code Subscriber}s and the respective + * terminal even is only emitted when all previous items have been successfully delivered to {@code Subscriber}s. + * If there are no {@code Subscriber}s, the remaining items will be buffered indefinitely. + * <p> + * The {@code MulticastProcessor} does not support clearing its cached events (to appear empty again). + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The backpressure from the currently subscribed {@code Subscriber}s are coordinated by emitting upstream + * items only if all of those {@code Subscriber}s have requested at least one item. This behavior + * is also called <em>lockstep-mode</em> because even if some {@code Subscriber}s can take any number + * of items, other {@code Subscriber}s requesting less or infrequently will slow down the overall + * throughput of the flow.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code MulticastProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on an arbitrary thread in a serialized fashion.</dd> + * </dl> + * <p> + * Example: + * <pre><code> + MulticastProcessor<Integer> mp = Flowable.range(1, 10) + .subscribeWith(MulticastProcessor.create()); + + mp.test().assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + // -------------------- + + MulticastProcessor<Integer> mp2 = MulticastProcessor.create(4); + mp2.start(); + + assertTrue(mp2.offer(1)); + assertTrue(mp2.offer(2)); + assertTrue(mp2.offer(3)); + assertTrue(mp2.offer(4)); + + assertFalse(mp2.offer(5)); + + mp2.onComplete(); + + mp2.test().assertResult(1, 2, 3, 4); + * </code></pre> + * <p>History: 2.1.14 - experimental + * @param <T> the input and output value type + * @since 2.2 + */ +@BackpressureSupport(BackpressureKind.FULL) +@SchedulerSupport(SchedulerSupport.NONE) +public final class MulticastProcessor<T> extends FlowableProcessor<T> { + + final AtomicInteger wip; + + final AtomicReference<Subscription> upstream; + + final AtomicReference<MulticastSubscription<T>[]> subscribers; + + final AtomicBoolean once; + + final int bufferSize; + + final int limit; + + final boolean refcount; + + volatile SimpleQueue<T> queue; + + volatile boolean done; + volatile Throwable error; + + int consumed; + + int fusionMode; + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] EMPTY = new MulticastSubscription[0]; + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] TERMINATED = new MulticastSubscription[0]; + + /** + * Constructs a fresh instance with the default Flowable.bufferSize() prefetch + * amount and no refCount-behavior. + * @param <T> the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static <T> MulticastProcessor<T> create() { + return new MulticastProcessor<T>(bufferSize(), false); + } + + /** + * Constructs a fresh instance with the default Flowable.bufferSize() prefetch + * amount and the optional refCount-behavior. + * @param <T> the input and output value type + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static <T> MulticastProcessor<T> create(boolean refCount) { + return new MulticastProcessor<T>(bufferSize(), refCount); + } + + /** + * Constructs a fresh instance with the given prefetch amount and no refCount behavior. + * @param bufferSize the prefetch amount + * @param <T> the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static <T> MulticastProcessor<T> create(int bufferSize) { + return new MulticastProcessor<T>(bufferSize, false); + } + + /** + * Constructs a fresh instance with the given prefetch amount and the optional + * refCount-behavior. + * @param bufferSize the prefetch amount + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + * @param <T> the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static <T> MulticastProcessor<T> create(int bufferSize, boolean refCount) { + return new MulticastProcessor<T>(bufferSize, refCount); + } + + /** + * Constructs a fresh instance with the given prefetch amount and the optional + * refCount-behavior. + * @param bufferSize the prefetch amount + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + */ + @SuppressWarnings("unchecked") + MulticastProcessor(int bufferSize, boolean refCount) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + this.bufferSize = bufferSize; + this.limit = bufferSize - (bufferSize >> 2); + this.wip = new AtomicInteger(); + this.subscribers = new AtomicReference<MulticastSubscription<T>[]>(EMPTY); + this.upstream = new AtomicReference<Subscription>(); + this.refcount = refCount; + this.once = new AtomicBoolean(); + } + + /** + * Initializes this Processor by setting an upstream Subscription that + * ignores request amounts, uses a fixed buffer + * and allows using the onXXX and offer methods + * afterwards. + */ + public void start() { + if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) { + queue = new SpscArrayQueue<T>(bufferSize); + } + } + + /** + * Initializes this Processor by setting an upstream Subscription that + * ignores request amounts, uses an unbounded buffer + * and allows using the onXXX and offer methods + * afterwards. + */ + public void startUnbounded() { + if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) { + queue = new SpscLinkedArrayQueue<T>(bufferSize); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription<T> qs = (QueueSubscription<T>)s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + fusionMode = m; + queue = qs; + + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue<T>(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + if (once.get()) { + return; + } + if (fusionMode == QueueSubscription.NONE) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (!queue.offer(t)) { + SubscriptionHelper.cancel(upstream); + onError(new MissingBackpressureException()); + return; + } + } + drain(); + } + + /** + * Tries to offer an item into the internal queue and returns false + * if the queue is full. + * @param t the item to offer, not null + * @return true if successful, false if the queue is full + */ + public boolean offer(T t) { + if (once.get()) { + return false; + } + ObjectHelper.requireNonNull(t, "offer called with null. Null values are generally not allowed in 2.x operators and sources."); + if (fusionMode == QueueSubscription.NONE) { + if (queue.offer(t)) { + drain(); + return true; + } + } + return false; + } + + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + error = t; + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + done = true; + drain(); + } + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + @Override + public boolean hasThrowable() { + return once.get() && error != null; + } + + @Override + public boolean hasComplete() { + return once.get() && error == null; + } + + @Override + public Throwable getThrowable() { + return once.get() ? error : null; + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + MulticastSubscription<T> ms = new MulticastSubscription<T>(s, this); + s.onSubscribe(ms); + if (add(ms)) { + if (ms.get() == Long.MIN_VALUE) { + remove(ms); + } else { + drain(); + } + } else { + if (once.get() || !refcount) { + Throwable ex = error; + if (ex != null) { + s.onError(ex); + return; + } + } + s.onComplete(); + } + } + + boolean add(MulticastSubscription<T> inner) { + for (;;) { + MulticastSubscription<T>[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + MulticastSubscription<T>[] b = new MulticastSubscription[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(MulticastSubscription<T> inner) { + for (;;) { + MulticastSubscription<T>[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + break; + } + + if (n == 1) { + if (refcount) { + if (subscribers.compareAndSet(a, TERMINATED)) { + SubscriptionHelper.cancel(upstream); + once.set(true); + break; + } + } else { + if (subscribers.compareAndSet(a, EMPTY)) { + break; + } + } + } else { + MulticastSubscription<T>[] b = new MulticastSubscription[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + if (subscribers.compareAndSet(a, b)) { + break; + } + } + } + } + + @SuppressWarnings("unchecked") + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + AtomicReference<MulticastSubscription<T>[]> subs = subscribers; + int c = consumed; + int lim = limit; + int fm = fusionMode; + + outer: + for (;;) { + + SimpleQueue<T> q = queue; + + if (q != null) { + MulticastSubscription<T>[] as = subs.get(); + int n = as.length; + + if (n != 0) { + long r = -1L; + + for (MulticastSubscription<T> a : as) { + long ra = a.get(); + if (ra >= 0L) { + if (r == -1L) { + r = ra - a.emitted; + } else { + r = Math.min(r, ra - a.emitted); + } + } + } + + while (r > 0L) { + MulticastSubscription<T>[] bs = subs.get(); + + if (bs == TERMINATED) { + q.clear(); + return; + } + + if (as != bs) { + continue outer; + } + + boolean d = done; + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + SubscriptionHelper.cancel(upstream); + d = true; + v = null; + error = ex; + done = true; + } + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + for (MulticastSubscription<T> inner : subs.getAndSet(TERMINATED)) { + inner.onError(ex); + } + } else { + for (MulticastSubscription<T> inner : subs.getAndSet(TERMINATED)) { + inner.onComplete(); + } + } + return; + } + + if (empty) { + break; + } + + for (MulticastSubscription<T> inner : as) { + inner.onNext(v); + } + + r--; + + if (fm != QueueSubscription.SYNC) { + if (++c == lim) { + c = 0; + upstream.get().request(lim); + } + } + } + + if (r == 0) { + MulticastSubscription<T>[] bs = subs.get(); + + if (bs == TERMINATED) { + q.clear(); + return; + } + + if (as != bs) { + continue outer; + } + + if (done && q.isEmpty()) { + Throwable ex = error; + if (ex != null) { + for (MulticastSubscription<T> inner : subs.getAndSet(TERMINATED)) { + inner.onError(ex); + } + } else { + for (MulticastSubscription<T> inner : subs.getAndSet(TERMINATED)) { + inner.onComplete(); + } + } + return; + } + } + } + } + + consumed = c; + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class MulticastSubscription<T> extends AtomicLong implements Subscription { + + private static final long serialVersionUID = -363282618957264509L; + + final Subscriber<? super T> downstream; + + final MulticastProcessor<T> parent; + + long emitted; + + MulticastSubscription(Subscriber<? super T> actual, MulticastProcessor<T> parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + long r = get(); + if (r == Long.MIN_VALUE || r == Long.MAX_VALUE) { + break; + } + long u = r + n; + if (u < 0L) { + u = Long.MAX_VALUE; + } + if (compareAndSet(r, u)) { + parent.drain(); + break; + } + } + } + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + } + } + + void onNext(T t) { + if (get() != Long.MIN_VALUE) { + emitted++; + downstream.onNext(t); + } + } + + void onError(Throwable t) { + if (get() != Long.MIN_VALUE) { + downstream.onError(t); + } + } + + void onComplete() { + if (get() != Long.MIN_VALUE) { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/processors/PublishProcessor.java b/src/main/java/io/reactivex/processors/PublishProcessor.java index 51ff0f6326..878ae92cbe 100644 --- a/src/main/java/io/reactivex/processors/PublishProcessor.java +++ b/src/main/java/io/reactivex/processors/PublishProcessor.java @@ -18,6 +18,7 @@ import io.reactivex.annotations.*; import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.internal.util.BackpressureHelper; import io.reactivex.plugins.RxJavaPlugins; @@ -26,21 +27,70 @@ * Processor that multicasts all subsequently observed items to its current {@link Subscriber}s. * * <p> - * <img width="640" height="405" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.PublishSubject.png" alt=""> - * - * <p>The processor does not coordinate backpressure for its subscribers and implements a weaker onSubscribe which - * calls requests Long.MAX_VALUE from the incoming Subscriptions. This makes it possible to subscribe the PublishProcessor - * to multiple sources (note on serialization though) unlike the standard Subscriber contract. Child subscribers, however, are not overflown but receive an - * IllegalStateException in case their requested amount is zero. - * - * <p>The implementation of onXXX methods are technically thread-safe but non-serialized calls - * to them may lead to undefined state in the currently subscribed Subscribers. - * - * <p>Due to the nature Flowables are constructed, the PublishProcessor can't be instantiated through - * {@code new} but must be created via the {@link #create()} method. + * <img width="640" height="278" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/PublishProcessor.png" alt=""> + * <p> + * This processor does not have a public constructor by design; a new empty instance of this + * {@code PublishProcessor} can be created via the {@link #create()} method. + * <p> + * Since a {@code PublishProcessor} is a Reactive Streams {@code Processor} type, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + * <p> + * {@code PublishProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor}, + * however, it does not coordinate backpressure between different subscribers and between an + * upstream source and a subscriber. If an upstream item is received via {@link #onNext(Object)}, if + * a subscriber is not ready to receive an item, that subscriber is terminated via a {@link MissingBackpressureException}. + * To avoid this case, use {@link #offer(Object)} and retry sometime later if it returned false. + * The {@code PublishProcessor}'s {@link Subscriber}-side consumes items in an unbounded manner. + * <p> + * For a multicasting processor type that also coordinates between the downstream {@code Subscriber}s and the upstream + * source as well, consider using {@link MulticastProcessor}. + * <p> + * When this {@code PublishProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Subscriber}s only receive the respective terminal event. + * <p> + * Unlike a {@link BehaviorProcessor}, a {@code PublishProcessor} doesn't retain/cache items, therefore, a new + * {@code Subscriber} won't receive any past items. + * <p> + * Even though {@code PublishProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code PublishProcessor} reached its terminal state will result in the + * given {@link Subscription} being canceled immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * Note that serializing over {@link #offer(Object)} is not supported through {@code toSerialized()} because it is a method + * available on the {@code PublishProcessor} and {@code BehaviorProcessor} classes only. + * <p> + * This {@code PublishProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>The processor does not coordinate backpressure for its subscribers and implements a weaker {@code onSubscribe} which + * calls requests Long.MAX_VALUE from the incoming Subscriptions. This makes it possible to subscribe the {@code PublishProcessor} + * to multiple sources (note on serialization though) unlike the standard {@code Subscriber} contract. Child subscribers, however, are not overflown but receive an + * {@link IllegalStateException} in case their requested amount is zero.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code PublishProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code PublishProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code PublishProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> * * Example usage: - * <p> * <pre> {@code PublishProcessor<Object> processor = PublishProcessor.create(); @@ -55,6 +105,7 @@ } </pre> * @param <T> the value type multicasted to Subscribers. + * @see MulticastProcessor */ public final class PublishProcessor<T> extends FlowableProcessor<T> { /** The terminated indicator for the subscribers array. */ @@ -76,6 +127,7 @@ public final class PublishProcessor<T> extends FlowableProcessor<T> { * @return the new PublishProcessor */ @CheckReturnValue + @NonNull public static <T> PublishProcessor<T> create() { return new PublishProcessor<T>(); } @@ -89,9 +141,8 @@ public static <T> PublishProcessor<T> create() { subscribers = new AtomicReference<PublishSubscription<T>[]>(EMPTY); } - @Override - public void subscribeActual(Subscriber<? super T> t) { + protected void subscribeActual(Subscriber<? super T> t) { PublishSubscription<T> ps = new PublishSubscription<T>(t, this); t.onSubscribe(ps); if (add(ps)) { @@ -112,9 +163,9 @@ public void subscribeActual(Subscriber<? super T> t) { /** * Tries to add the given subscriber to the subscribers array atomically - * or returns false if the subject has terminated. + * or returns false if this processor has terminated. * @param ps the subscriber to add - * @return true if successful, false if the subject has terminated + * @return true if successful, false if this processor has terminated */ boolean add(PublishSubscription<T> ps) { for (;;) { @@ -136,8 +187,8 @@ boolean add(PublishSubscription<T> ps) { } /** - * Atomically removes the given subscriber if it is subscribed to the subject. - * @param ps the subject to remove + * Atomically removes the given subscriber if it is subscribed to this processor. + * @param ps the subscription wrapping a subscriber to remove */ @SuppressWarnings("unchecked") void remove(PublishSubscription<T> ps) { @@ -181,19 +232,13 @@ public void onSubscribe(Subscription s) { s.cancel(); return; } - // PublishSubject doesn't bother with request coordination. + // PublishProcessor doesn't bother with request coordination. s.request(Long.MAX_VALUE); } @Override public void onNext(T t) { - if (subscribers.get() == TERMINATED) { - return; - } - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); for (PublishSubscription<T> s : subscribers.get()) { s.onNext(t); } @@ -202,13 +247,11 @@ public void onNext(T t) { @SuppressWarnings("unchecked") @Override public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { RxJavaPlugins.onError(t); return; } - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } error = t; for (PublishSubscription<T> s : subscribers.getAndSet(TERMINATED)) { @@ -236,11 +279,11 @@ public void onComplete() { * <p> * Calling with null will terminate the PublishProcessor and a NullPointerException * is signalled to the Subscribers. + * <p>History: 2.0.8 - experimental * @param t the item to emit, not null * @return true if the item was emitted to all Subscribers - * @since 2.0.8 - experimental + * @since 2.2 */ - @Experimental public boolean offer(T t) { if (t == null) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); @@ -266,6 +309,7 @@ public boolean hasSubscribers() { } @Override + @Nullable public Throwable getThrowable() { if (subscribers.get() == TERMINATED) { return error; @@ -293,8 +337,8 @@ static final class PublishSubscription<T> extends AtomicLong implements Subscrip private static final long serialVersionUID = 3562861878281475070L; /** The actual subscriber. */ - final Subscriber<? super T> actual; - /** The subject state. */ + final Subscriber<? super T> downstream; + /** The parent processor servicing this subscriber. */ final PublishProcessor<T> parent; /** @@ -303,7 +347,7 @@ static final class PublishSubscription<T> extends AtomicLong implements Subscrip * @param parent the parent PublishProcessor */ PublishSubscription(Subscriber<? super T> actual, PublishProcessor<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } @@ -313,19 +357,17 @@ public void onNext(T t) { return; } if (r != 0L) { - actual.onNext(t); - if (r != Long.MAX_VALUE) { - decrementAndGet(); - } + downstream.onNext(t); + BackpressureHelper.producedCancel(this, 1); } else { cancel(); - actual.onError(new MissingBackpressureException("Could not emit value due to lack of requests")); + downstream.onError(new MissingBackpressureException("Could not emit value due to lack of requests")); } } public void onError(Throwable t) { if (get() != Long.MIN_VALUE) { - actual.onError(t); + downstream.onError(t); } else { RxJavaPlugins.onError(t); } @@ -333,7 +375,7 @@ public void onError(Throwable t) { public void onComplete() { if (get() != Long.MIN_VALUE) { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/processors/ReplayProcessor.java b/src/main/java/io/reactivex/processors/ReplayProcessor.java index c8650e3981..b04a17d07e 100644 --- a/src/main/java/io/reactivex/processors/ReplayProcessor.java +++ b/src/main/java/io/reactivex/processors/ReplayProcessor.java @@ -13,7 +13,6 @@ package io.reactivex.processors; -import io.reactivex.annotations.CheckReturnValue; import java.lang.reflect.Array; import java.util.*; import java.util.concurrent.TimeUnit; @@ -22,6 +21,7 @@ import org.reactivestreams.*; import io.reactivex.Scheduler; +import io.reactivex.annotations.*; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.internal.util.*; @@ -30,21 +30,101 @@ /** * Replays events to Subscribers. * <p> - * <img width="640" height="405" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.ReplaySubject.png" alt=""> - * + * The {@code ReplayProcessor} supports the following item retainment strategies: + * <ul> + * <li>{@link #create()} and {@link #create(int)}: retains and replays all events to current and + * future {@code Subscriber}s. + * <p> + * <img width="640" height="269" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplayProcessor.u.png" alt=""> + * <p> + * <img width="640" height="345" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplayProcessor.ue.png" alt=""> + * </li> + * <li>{@link #createWithSize(int)}: retains at most the given number of items and replays only these + * latest items to new {@code Subscriber}s. + * <p> + * <img width="640" height="332" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplayProcessor.n.png" alt=""> + * </li> + * <li>{@link #createWithTime(long, TimeUnit, Scheduler)}: retains items no older than the specified time + * and replays them to new {@code Subscriber}s (which could mean all items age out). * <p> - * The ReplayProcessor can be created in bounded and unbounded mode. It can be bounded by + * <img width="640" height="415" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplayProcessor.t.png" alt=""> + * </li> + * <li>{@link #createWithTimeAndSize(long, TimeUnit, Scheduler, int)}: retains no more than the given number of items + * which are also no older than the specified time and replays them to new {@code Subscriber}s (which could mean all items age out). + * <p> + * <img width="640" height="404" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplayProcessor.nt.png" alt=""> + * </li> + * </ul> + * <p> + * The {@code ReplayProcessor} can be created in bounded and unbounded mode. It can be bounded by * size (maximum number of elements retained at most) and/or time (maximum age of elements replayed). - * - * <p>This Processor respects the backpressure behavior of its Subscribers (individually) but - * does not coordinate their request amounts towards the upstream (because there might not be any). - * - * <p>Note that Subscribers receive a continuous sequence of values after they subscribed even + * <p> + * Since a {@code ReplayProcessor} is a Reactive Streams {@code Processor}, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + * <p> + * This {@code ReplayProcessor} respects the individual backpressure behavior of its {@code Subscriber}s but + * does not coordinate their request amounts towards the upstream (because there might not be any) and + * consumes the upstream in an unbounded manner (requesting {@code Long.MAX_VALUE}). + * Note that {@code Subscriber}s receive a continuous sequence of values after they subscribed even * if an individual item gets delayed due to backpressure. - * + * Due to concurrency requirements, a size-bounded {@code ReplayProcessor} may hold strong references to more source + * emissions than specified. * <p> - * Example usage: + * When this {@code ReplayProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Subscriber}s will receive the retained/cached items first (if any) followed by the respective + * terminal event. If the {@code ReplayProcessor} has a time-bound, the age of the retained/cached items are still considered + * when replaying and thus it may result in no items being emitted before the terminal event. + * <p> + * Once an {@code Subscriber} has subscribed, it will receive items continuously from that point on. Bounds only affect how + * many past items a new {@code Subscriber} will receive before it catches up with the live event feed. + * <p> + * Even though {@code ReplayProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code ReplayProcessor} reached its terminal state will result in the + * given {@code Subscription} being canceled immediately. * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * <p> + * This {@code ReplayProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the retained/cached items + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + * <p> + * Note that due to concurrency requirements, a size- and time-bounded {@code ReplayProcessor} may hold strong references to more + * source emissions than specified while it isn't terminated yet. Use the {@link #cleanupBuffer()} to allow + * such inaccessible items to be cleaned up by GC once no consumer references them anymore. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>This {@code ReplayProcessor} respects the individual backpressure behavior of its {@code Subscriber}s but + * does not coordinate their request amounts towards the upstream (because there might not be any) and + * consumes the upstream in an unbounded manner (requesting {@code Long.MAX_VALUE}). + * Note that {@code Subscriber}s receive a continuous sequence of values after they subscribed even + * if an individual item gets delayed due to backpressure.</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code ReplayProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked. + * Time-bound {@code ReplayProcessor}s use the given {@code Scheduler} in their {@code create} methods + * as time source to timestamp of items received for the age checks.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code ReplayProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code ReplayProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: * <pre> {@code ReplayProcessor<Object> processor = new ReplayProcessor<T>(); @@ -91,6 +171,7 @@ public final class ReplayProcessor<T> extends FlowableProcessor<T> { * @return the created ReplayProcessor */ @CheckReturnValue + @NonNull public static <T> ReplayProcessor<T> create() { return new ReplayProcessor<T>(new UnboundedReplayBuffer<T>(16)); } @@ -105,12 +186,13 @@ public static <T> ReplayProcessor<T> create() { * due to frequent array-copying. * * @param <T> - * the type of items observed and emitted by the Subject + * the type of items observed and emitted by this type of processor * @param capacityHint * the initial buffer capacity - * @return the created subject + * @return the created processor */ @CheckReturnValue + @NonNull public static <T> ReplayProcessor<T> create(int capacityHint) { return new ReplayProcessor<T>(new UnboundedReplayBuffer<T>(capacityHint)); } @@ -121,21 +203,22 @@ public static <T> ReplayProcessor<T> create(int capacityHint) { * In this setting, the {@code ReplayProcessor} holds at most {@code size} items in its internal buffer and * discards the oldest item. * <p> - * When observers subscribe to a terminated {@code ReplayProcessor}, they are guaranteed to see at most + * When {@code Subscriber}s subscribe to a terminated {@code ReplayProcessor}, they are guaranteed to see at most * {@code size} {@code onNext} events followed by a termination event. * <p> - * If an observer subscribes while the {@code ReplayProcessor} is active, it will observe all items in the + * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe all items in the * buffer at that point in time and each item observed afterwards, even if the buffer evicts items due to - * the size constraint in the mean time. In other words, once an Observer subscribes, it will receive items + * the size constraint in the mean time. In other words, once a {@code Subscriber} subscribes, it will receive items * without gaps in the sequence. * * @param <T> - * the type of items observed and emitted by the Subject + * the type of items observed and emitted by this type of processor * @param maxSize * the maximum number of buffered items - * @return the created subject + * @return the created processor */ @CheckReturnValue + @NonNull public static <T> ReplayProcessor<T> createWithSize(int maxSize) { return new ReplayProcessor<T>(new SizeBoundReplayBuffer<T>(maxSize)); } @@ -150,8 +233,8 @@ public static <T> ReplayProcessor<T> createWithSize(int maxSize) { * of the bounded implementations without the interference of the eviction policies. * * @param <T> - * the type of items observed and emitted by the Subject - * @return the created subject + * the type of items observed and emitted by this type of processor + * @return the created processor */ /* test */ static <T> ReplayProcessor<T> createUnbounded() { return new ReplayProcessor<T>(new SizeBoundReplayBuffer<T>(Integer.MAX_VALUE)); @@ -165,31 +248,32 @@ public static <T> ReplayProcessor<T> createWithSize(int maxSize) { * converted to milliseconds. For example, an item arrives at T=0 and the max age is set to 5; at T>=5 * this first item is then evicted by any subsequent item or termination event, leaving the buffer empty. * <p> - * Once the subject is terminated, observers subscribing to it will receive items that remained in the + * Once the processor is terminated, {@code Subscriber}s subscribing to it will receive items that remained in the * buffer after the terminal event, regardless of their age. * <p> - * If an observer subscribes while the {@code ReplayProcessor} is active, it will observe only those items + * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe only those items * from within the buffer that have an age less than the specified time, and each item observed thereafter, - * even if the buffer evicts items due to the time constraint in the mean time. In other words, once an - * observer subscribes, it observes items without gaps in the sequence except for any outdated items at the + * even if the buffer evicts items due to the time constraint in the mean time. In other words, once a + * {@code Subscriber} subscribes, it observes items without gaps in the sequence except for any outdated items at the * beginning of the sequence. * <p> * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification - * arrives at T=10. If an observer subscribes at T=11, it will find an empty {@code ReplayProcessor} with just + * arrives at T=10. If a {@code Subscriber} subscribes at T=11, it will find an empty {@code ReplayProcessor} with just * an {@code onComplete} notification. * * @param <T> - * the type of items observed and emitted by the Subject + * the type of items observed and emitted by this type of processor * @param maxAge * the maximum age of the contained items * @param unit * the time unit of {@code time} * @param scheduler * the {@link Scheduler} that provides the current time - * @return the created subject + * @return the created processor */ @CheckReturnValue + @NonNull public static <T> ReplayProcessor<T> createWithTime(long maxAge, TimeUnit unit, Scheduler scheduler) { return new ReplayProcessor<T>(new SizeAndTimeBoundReplayBuffer<T>(Integer.MAX_VALUE, maxAge, unit, scheduler)); } @@ -202,22 +286,22 @@ public static <T> ReplayProcessor<T> createWithTime(long maxAge, TimeUnit unit, * items from the start of the buffer if their age becomes less-than or equal to the supplied age in * milliseconds or the buffer reaches its {@code size} limit. * <p> - * When observers subscribe to a terminated {@code ReplayProcessor}, they observe the items that remained in + * When {@code Subscriber}s subscribe to a terminated {@code ReplayProcessor}, they observe the items that remained in * the buffer after the terminal notification, regardless of their age, but at most {@code size} items. * <p> - * If an observer subscribes while the {@code ReplayProcessor} is active, it will observe only those items + * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe only those items * from within the buffer that have age less than the specified time and each subsequent item, even if the - * buffer evicts items due to the time constraint in the mean time. In other words, once an observer + * buffer evicts items due to the time constraint in the mean time. In other words, once a {@code Subscriber} * subscribes, it observes items without gaps in the sequence except for the outdated items at the beginning * of the sequence. * <p> * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification - * arrives at T=10. If an observer subscribes at T=11, it will find an empty {@code ReplayProcessor} with just + * arrives at T=10. If a {@code Subscriber} subscribes at T=11, it will find an empty {@code ReplayProcessor} with just * an {@code onComplete} notification. * * @param <T> - * the type of items observed and emitted by the Subject + * the type of items observed and emitted by this type of processor * @param maxAge * the maximum age of the contained items * @param unit @@ -226,9 +310,10 @@ public static <T> ReplayProcessor<T> createWithTime(long maxAge, TimeUnit unit, * the maximum number of buffered items * @param scheduler * the {@link Scheduler} that provides the current time - * @return the created subject + * @return the created processor */ @CheckReturnValue + @NonNull public static <T> ReplayProcessor<T> createWithTimeAndSize(long maxAge, TimeUnit unit, Scheduler scheduler, int maxSize) { return new ReplayProcessor<T>(new SizeAndTimeBoundReplayBuffer<T>(maxSize, maxAge, unit, scheduler)); } @@ -268,16 +353,14 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done) { return; } ReplayBuffer<T> b = buffer; - b.add(t); + b.next(t); for (ReplaySubscription<T> rs : subscribers.get()) { b.replay(rs); @@ -287,20 +370,17 @@ public void onNext(T t) { @SuppressWarnings("unchecked") @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done) { RxJavaPlugins.onError(t); return; } done = true; - Object o = NotificationLite.error(t); - ReplayBuffer<T> b = buffer; + b.error(t); - b.addFinal(o); for (ReplaySubscription<T> rs : subscribers.getAndSet(TERMINATED)) { b.replay(rs); } @@ -314,11 +394,9 @@ public void onComplete() { } done = true; - Object o = NotificationLite.complete(); - ReplayBuffer<T> b = buffer; - b.addFinal(o); + b.complete(); for (ReplaySubscription<T> rs : subscribers.getAndSet(TERMINATED)) { b.replay(rs); @@ -335,27 +413,46 @@ public boolean hasSubscribers() { } @Override + @Nullable public Throwable getThrowable() { - Object o = buffer.get(); - if (NotificationLite.isError(o)) { - return NotificationLite.getError(o); + ReplayBuffer<T> b = buffer; + if (b.isDone()) { + return b.getError(); } return null; } /** - * Returns a single value the Subject currently has or null if no such value exists. + * Makes sure the item cached by the head node in a bounded + * ReplayProcessor is released (as it is never part of a replay). + * <p> + * By default, live bounded buffers will remember one item before + * the currently receivable one to ensure subscribers can always + * receive a continuous sequence of items. A terminated ReplayProcessor + * automatically releases this inaccessible item. + * <p> + * The method must be called sequentially, similar to the standard + * {@code onXXX} methods. + * <p>History: 2.1.11 - experimental + * @since 2.2 + */ + public void cleanupBuffer() { + buffer.trimHead(); + } + + /** + * Returns the latest value this processor has or null if no such value exists. * <p>The method is thread-safe. - * @return a single value the Subject currently has or null if no such value exists + * @return the latest value this processor currently has or null if no such value exists */ public T getValue() { return buffer.getValue(); } /** - * Returns an Object array containing snapshot all values of the Subject. + * Returns an Object array containing snapshot all values of this processor. * <p>The method is thread-safe. - * @return the array containing the snapshot of all values of the Subject + * @return the array containing the snapshot of all values of this processor */ public Object[] getValues() { @SuppressWarnings("unchecked") @@ -369,7 +466,7 @@ public Object[] getValues() { } /** - * Returns a typed array containing a snapshot of all values of the Subject. + * Returns a typed array containing a snapshot of all values of this processor. * <p>The method follows the conventions of Collection.toArray by setting the array element * after the last value to null (if the capacity permits). * <p>The method is thread-safe. @@ -382,20 +479,20 @@ public T[] getValues(T[] array) { @Override public boolean hasComplete() { - Object o = buffer.get(); - return NotificationLite.isComplete(o); + ReplayBuffer<T> b = buffer; + return b.isDone() && b.getError() == null; } @Override public boolean hasThrowable() { - Object o = buffer.get(); - return NotificationLite.isError(o); + ReplayBuffer<T> b = buffer; + return b.isDone() && b.getError() != null; } /** - * Returns true if the subject has any value. + * Returns true if this processor has any value. * <p>The method is thread-safe. - * @return true if the subject has any value + * @return true if the processor has any value */ public boolean hasValue() { return buffer.size() != 0; // NOPMD @@ -463,29 +560,36 @@ void remove(ReplaySubscription<T> rs) { */ interface ReplayBuffer<T> { - void add(T value); + void next(T value); + + void error(Throwable ex); - void addFinal(Object notificationLite); + void complete(); void replay(ReplaySubscription<T> rs); int size(); + @Nullable T getValue(); T[] getValues(T[] array); + boolean isDone(); + + Throwable getError(); + /** - * Returns the terminal NotificationLite object or null if not yet terminated. - * @return the terminal NotificationLite object or null if not yet terminated + * Make sure an old inaccessible head value is released + * in a bounded buffer. */ - Object get(); + void trimHead(); } static final class ReplaySubscription<T> extends AtomicInteger implements Subscription { private static final long serialVersionUID = 466549804534799122L; - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final ReplayProcessor<T> state; Object index; @@ -494,11 +598,14 @@ static final class ReplaySubscription<T> extends AtomicInteger implements Subscr volatile boolean cancelled; + long emitted; + ReplaySubscription(Subscriber<? super T> actual, ReplayProcessor<T> state) { - this.actual = actual; + this.downstream = actual; this.state = state; this.requested = new AtomicLong(); } + @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { @@ -517,51 +624,49 @@ public void cancel() { } static final class UnboundedReplayBuffer<T> - extends AtomicReference<Object> implements ReplayBuffer<T> { - private static final long serialVersionUID = -4457200895834877300L; - - final List<Object> buffer; + final List<T> buffer; + Throwable error; volatile boolean done; volatile int size; UnboundedReplayBuffer(int capacityHint) { - this.buffer = new ArrayList<Object>(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + this.buffer = new ArrayList<T>(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); } @Override - public void add(T value) { + public void next(T value) { buffer.add(value); size++; } @Override - public void addFinal(Object notificationLite) { - lazySet(notificationLite); - buffer.add(notificationLite); - size++; + public void error(Throwable ex) { + error = ex; done = true; } @Override - @SuppressWarnings("unchecked") + public void complete() { + done = true; + } + + @Override + public void trimHead() { + // not applicable for an unbounded buffer + } + + @Override + @Nullable public T getValue() { int s = size; - if (s != 0) { - List<Object> b = buffer; - Object o = b.get(s - 1); - if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { - if (s == 1) { - return null; - } - return (T)b.get(s - 2); - } - return (T)o; + if (s == 0) { + return null; } - return null; + return buffer.get(s - 1); } @Override @@ -574,25 +679,13 @@ public T[] getValues(T[] array) { } return array; } - List<Object> b = buffer; - Object o = b.get(s - 1); - - if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { - s--; - if (s == 0) { - if (array.length != 0) { - array[0] = null; - } - return array; - } - } - + List<T> b = buffer; if (array.length < s) { array = (T[])Array.newInstance(array.getClass().getComponentType(), s); } for (int i = 0; i < s; i++) { - array[i] = (T)b.get(i); + array[i] = b.get(i); } if (array.length > s) { array[s] = null; @@ -602,15 +695,14 @@ public T[] getValues(T[] array) { } @Override - @SuppressWarnings("unchecked") public void replay(ReplaySubscription<T> rs) { if (rs.getAndIncrement() != 0) { return; } int missed = 1; - final List<Object> b = buffer; - final Subscriber<? super T> a = rs.actual; + final List<T> b = buffer; + final Subscriber<? super T> a = rs.downstream; Integer indexObject = (Integer)rs.index; int index; @@ -620,67 +712,67 @@ public void replay(ReplaySubscription<T> rs) { index = 0; rs.index = 0; } + long e = rs.emitted; for (;;) { - if (rs.cancelled) { - rs.index = null; - return; - } - - int s = size; long r = rs.requested.get(); - long e = 0L; - - while (s != index) { + while (e != r) { if (rs.cancelled) { rs.index = null; return; } - Object o = b.get(index); - - if (done) { - if (index + 1 == s) { - s = size; - if (index + 1 == s) { - if (NotificationLite.isComplete(o)) { - a.onComplete(); - } else { - a.onError(NotificationLite.getError(o)); - } - rs.index = null; - rs.cancelled = true; - return; - } + boolean d = done; + int s = size; + + if (d && index == s) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); } + return; } - if (r == 0) { - r = rs.requested.get() + e; - if (r == 0) { - break; - } + if (index == s) { + break; } - a.onNext((T)o); - r--; - e--; + a.onNext(b.get(index)); + index++; + e++; } - if (e != 0L) { - if (rs.requested.get() != Long.MAX_VALUE) { - r = rs.requested.addAndGet(e); + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + int s = size; + + if (d && index == s) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; } - } - if (index != size && r != 0L) { - continue; } rs.index = index; - + rs.emitted = e; missed = rs.addAndGet(-missed); if (missed == 0) { break; @@ -690,15 +782,17 @@ public void replay(ReplaySubscription<T> rs) { @Override public int size() { - int s = size; - if (s != 0) { - Object o = buffer.get(s - 1); - if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { - return s - 1; - } - return s; - } - return 0; + return size; + } + + @Override + public boolean isDone() { + return done; + } + + @Override + public Throwable getError() { + return error; } } @@ -727,22 +821,21 @@ static final class TimedNode<T> extends AtomicReference<TimedNode<T>> { } static final class SizeBoundReplayBuffer<T> - extends AtomicReference<Object> implements ReplayBuffer<T> { - private static final long serialVersionUID = 3027920763113911982L; final int maxSize; int size; - volatile Node<Object> head; + volatile Node<T> head; - Node<Object> tail; + Node<T> tail; + Throwable error; volatile boolean done; SizeBoundReplayBuffer(int maxSize) { this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); - Node<Object> h = new Node<Object>(null); + Node<T> h = new Node<T>(null); this.tail = h; this.head = h; } @@ -750,15 +843,15 @@ static final class SizeBoundReplayBuffer<T> void trim() { if (size > maxSize) { size--; - Node<Object> h = head; + Node<T> h = head; head = h.get(); } } @Override - public void add(T value) { - Node<Object> n = new Node<Object>(value); - Node<Object> t = tail; + public void next(T value) { + Node<T> n = new Node<T>(value); + Node<T> t = tail; tail = n; size++; @@ -768,71 +861,75 @@ public void add(T value) { } @Override - public void addFinal(Object notificationLite) { - lazySet(notificationLite); - Node<Object> n = new Node<Object>(notificationLite); - Node<Object> t = tail; - - tail = n; - size++; - t.set(n); // releases both the tail and size + public void error(Throwable ex) { + error = ex; + trimHead(); + done = true; + } + @Override + public void complete() { + trimHead(); done = true; } @Override - @SuppressWarnings("unchecked") - public T getValue() { - Node<Object> prev = null; - Node<Object> h = head; + public void trimHead() { + if (head.value != null) { + Node<T> n = new Node<T>(null); + n.lazySet(head.get()); + head = n; + } + } + @Override + public boolean isDone() { + return done; + } + + @Override + public Throwable getError() { + return error; + } + + @Override + public T getValue() { + Node<T> h = head; for (;;) { - Node<Object> next = h.get(); - if (next == null) { - break; + Node<T> n = h.get(); + if (n == null) { + return h.value; } - prev = h; - h = next; + h = n; } - - Object v = h.value; - if (v == null) { - return null; - } - if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { - return (T)prev.value; - } - - return (T)v; } @Override @SuppressWarnings("unchecked") public T[] getValues(T[] array) { - Node<Object> h = head; - int s = size(); - - if (s == 0) { - if (array.length != 0) { - array[0] = null; - } - } else { - if (array.length < s) { - array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + int s = 0; + Node<T> h = head; + Node<T> h0 = h; + for (;;) { + Node<T> next = h0.get(); + if (next == null) { + break; } + s++; + h0 = next; + } + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } - int i = 0; - while (i != s) { - Node<Object> next = h.get(); - array[i] = (T)next.value; - i++; - h = next; - } - if (array.length > s) { - array[s] = null; - } + for (int j = 0; j < s; j++) { + h = h.get(); + array[j] = h.value; } + if (array.length > s) { + array[s] = null; + } return array; } @@ -844,67 +941,73 @@ public void replay(ReplaySubscription<T> rs) { } int missed = 1; - final Subscriber<? super T> a = rs.actual; + final Subscriber<? super T> a = rs.downstream; - Node<Object> index = (Node<Object>)rs.index; + Node<T> index = (Node<T>)rs.index; if (index == null) { index = head; } + long e = rs.emitted; + for (;;) { long r = rs.requested.get(); - long e = 0; - for (;;) { + while (e != r) { if (rs.cancelled) { rs.index = null; return; } - Node<Object> n = index.get(); - - if (n == null) { - break; - } - - Object o = n.value; - - if (done) { - if (n.get() == null) { + boolean d = done; + Node<T> next = index.get(); + boolean empty = next == null; - if (NotificationLite.isComplete(o)) { - a.onComplete(); - } else { - a.onError(NotificationLite.getError(o)); - } - rs.index = null; - rs.cancelled = true; - return; + if (d && empty) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); } + return; } - if (r == 0) { - r = rs.requested.get() + e; - if (r == 0) { - break; - } + if (empty) { + break; } - a.onNext((T)o); - r--; - e--; - - index = n; + a.onNext(next.value); + e++; + index = next; } - if (e != 0L) { - if (rs.requested.get() != Long.MAX_VALUE) { - rs.requested.addAndGet(e); + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + + if (d && index.get() == null) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; } } rs.index = index; + rs.emitted = e; missed = rs.addAndGet(-missed); if (missed == 0) { @@ -916,14 +1019,10 @@ public void replay(ReplaySubscription<T> rs) { @Override public int size() { int s = 0; - Node<Object> h = head; + Node<T> h = head; while (s != Integer.MAX_VALUE) { - Node<Object> next = h.get(); + Node<T> next = h.get(); if (next == null) { - Object o = h.value; - if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { - s--; - } break; } s++; @@ -935,30 +1034,27 @@ public int size() { } static final class SizeAndTimeBoundReplayBuffer<T> - extends AtomicReference<Object> implements ReplayBuffer<T> { - private static final long serialVersionUID = 1242561386470847675L; - final int maxSize; final long maxAge; final TimeUnit unit; final Scheduler scheduler; int size; - volatile TimedNode<Object> head; + volatile TimedNode<T> head; - TimedNode<Object> tail; + TimedNode<T> tail; + Throwable error; volatile boolean done; - SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); this.unit = ObjectHelper.requireNonNull(unit, "unit is null"); this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null"); - TimedNode<Object> h = new TimedNode<Object>(null, 0L); + TimedNode<T> h = new TimedNode<T>(null, 0L); this.tail = h; this.head = h; } @@ -966,15 +1062,19 @@ static final class SizeAndTimeBoundReplayBuffer<T> void trim() { if (size > maxSize) { size--; - TimedNode<Object> h = head; + TimedNode<T> h = head; head = h.get(); } long limit = scheduler.now(unit) - maxAge; - TimedNode<Object> h = head; + TimedNode<T> h = head; for (;;) { - TimedNode<Object> next = h.get(); + if (size <= 1) { + head = h; + break; + } + TimedNode<T> next = h.get(); if (next == null) { head = h; break; @@ -986,6 +1086,7 @@ void trim() { } h = next; + size--; } } @@ -993,17 +1094,27 @@ void trim() { void trimFinal() { long limit = scheduler.now(unit) - maxAge; - TimedNode<Object> h = head; + TimedNode<T> h = head; for (;;) { - TimedNode<Object> next = h.get(); - if (next.get() == null) { - head = h; + TimedNode<T> next = h.get(); + if (next == null) { + if (h.value != null) { + head = new TimedNode<T>(null, 0L); + } else { + head = h; + } break; } if (next.time > limit) { - head = h; + if (h.value != null) { + TimedNode<T> n = new TimedNode<T>(null, 0L); + n.lazySet(h.get()); + head = n; + } else { + head = h; + } break; } @@ -1012,9 +1123,18 @@ void trimFinal() { } @Override - public void add(T value) { - TimedNode<Object> n = new TimedNode<Object>(value, scheduler.now(unit)); - TimedNode<Object> t = tail; + public void trimHead() { + if (head.value != null) { + TimedNode<T> n = new TimedNode<T>(null, 0L); + n.lazySet(head.get()); + head = n; + } + } + + @Override + public void next(T value) { + TimedNode<T> n = new TimedNode<T>(value, scheduler.now(unit)); + TimedNode<T> t = tail; tail = n; size++; @@ -1024,31 +1144,28 @@ public void add(T value) { } @Override - public void addFinal(Object notificationLite) { - lazySet(notificationLite); - TimedNode<Object> n = new TimedNode<Object>(notificationLite, Long.MAX_VALUE); - TimedNode<Object> t = tail; - - tail = n; - size++; - t.set(n); // releases both the tail and size + public void error(Throwable ex) { trimFinal(); + error = ex; + done = true; + } + @Override + public void complete() { + trimFinal(); done = true; } @Override - @SuppressWarnings("unchecked") + @Nullable public T getValue() { - TimedNode<Object> prev = null; - TimedNode<Object> h = head; + TimedNode<T> h = head; for (;;) { - TimedNode<Object> next = h.get(); + TimedNode<T> next = h.get(); if (next == null) { break; } - prev = h; h = next; } @@ -1057,21 +1174,13 @@ public T getValue() { return null; } - Object v = h.value; - if (v == null) { - return null; - } - if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { - return (T)prev.value; - } - - return (T)v; + return h.value; } @Override @SuppressWarnings("unchecked") public T[] getValues(T[] array) { - TimedNode<Object> h = getHead(); + TimedNode<T> h = getHead(); int s = size(h); if (s == 0) { @@ -1085,8 +1194,8 @@ public T[] getValues(T[] array) { int i = 0; while (i != s) { - TimedNode<Object> next = h.get(); - array[i] = (T)next.value; + TimedNode<T> next = h.get(); + array[i] = next.value; i++; h = next; } @@ -1098,11 +1207,11 @@ public T[] getValues(T[] array) { return array; } - TimedNode<Object> getHead() { - TimedNode<Object> index = head; + TimedNode<T> getHead() { + TimedNode<T> index = head; // skip old entries long limit = scheduler.now(unit) - maxAge; - TimedNode<Object> next = index.get(); + TimedNode<T> next = index.get(); while (next != null) { long ts = next.time; if (ts > limit) { @@ -1122,67 +1231,73 @@ public void replay(ReplaySubscription<T> rs) { } int missed = 1; - final Subscriber<? super T> a = rs.actual; + final Subscriber<? super T> a = rs.downstream; - TimedNode<Object> index = (TimedNode<Object>)rs.index; + TimedNode<T> index = (TimedNode<T>)rs.index; if (index == null) { index = getHead(); } + long e = rs.emitted; + for (;;) { long r = rs.requested.get(); - long e = 0; - for (;;) { + while (e != r) { if (rs.cancelled) { rs.index = null; return; } - TimedNode<Object> n = index.get(); - - if (n == null) { - break; - } + boolean d = done; + TimedNode<T> next = index.get(); + boolean empty = next == null; - Object o = n.value; - - if (done) { - if (n.get() == null) { - - if (NotificationLite.isComplete(o)) { - a.onComplete(); - } else { - a.onError(NotificationLite.getError(o)); - } - rs.index = null; - rs.cancelled = true; - return; + if (d && empty) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); } + return; } - if (r == 0) { - r = rs.requested.get() + e; - if (r == 0) { - break; - } + if (empty) { + break; } - a.onNext((T)o); - r--; - e--; - - index = n; + a.onNext(next.value); + e++; + index = next; } - if (e != 0L) { - if (rs.requested.get() != Long.MAX_VALUE) { - rs.requested.addAndGet(e); + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + + if (d && index.get() == null) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; } } rs.index = index; + rs.emitted = e; missed = rs.addAndGet(-missed); if (missed == 0) { @@ -1196,15 +1311,11 @@ public int size() { return size(getHead()); } - int size(TimedNode<Object> h) { + int size(TimedNode<T> h) { int s = 0; while (s != Integer.MAX_VALUE) { - TimedNode<Object> next = h.get(); + TimedNode<T> next = h.get(); if (next == null) { - Object o = h.value; - if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { - s--; - } break; } s++; @@ -1213,5 +1324,15 @@ int size(TimedNode<Object> h) { return s; } + + @Override + public Throwable getError() { + return error; + } + + @Override + public boolean isDone() { + return done; + } } } diff --git a/src/main/java/io/reactivex/processors/SerializedProcessor.java b/src/main/java/io/reactivex/processors/SerializedProcessor.java index 7a755500b9..8c0a37e02a 100644 --- a/src/main/java/io/reactivex/processors/SerializedProcessor.java +++ b/src/main/java/io/reactivex/processors/SerializedProcessor.java @@ -13,6 +13,7 @@ package io.reactivex.processors; +import io.reactivex.annotations.Nullable; import org.reactivestreams.*; import io.reactivex.internal.util.*; @@ -187,6 +188,7 @@ public boolean hasThrowable() { } @Override + @Nullable public Throwable getThrowable() { return actual.getThrowable(); } diff --git a/src/main/java/io/reactivex/processors/UnicastProcessor.java b/src/main/java/io/reactivex/processors/UnicastProcessor.java index 8334aa07c5..ec4d6c1a77 100644 --- a/src/main/java/io/reactivex/processors/UnicastProcessor.java +++ b/src/main/java/io/reactivex/processors/UnicastProcessor.java @@ -16,8 +16,8 @@ import io.reactivex.annotations.CheckReturnValue; import java.util.concurrent.atomic.*; -import io.reactivex.annotations.Experimental; import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; import org.reactivestreams.*; import io.reactivex.internal.functions.ObjectHelper; @@ -28,18 +28,122 @@ import io.reactivex.plugins.RxJavaPlugins; /** - * Processor that allows only a single Subscriber to subscribe to it during its lifetime. - * - * <p>This processor buffers notifications and replays them to the Subscriber as requested. - * - * <p>This processor holds an unbounded internal buffer. - * - * <p>If more than one Subscriber attempts to subscribe to this Processor, they - * will receive an IllegalStateException if this Processor hasn't terminated yet, + * A {@link FlowableProcessor} variant that queues up events until a single {@link Subscriber} subscribes to it, replays + * those events to it until the {@code Subscriber} catches up and then switches to relaying events live to + * this single {@code Subscriber} until this {@code UnicastProcessor} terminates or the {@code Subscriber} cancels + * its subscription. + * <p> + * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/UnicastProcessor.png" alt=""> + * <p> + * This processor does not have a public constructor by design; a new empty instance of this + * {@code UnicastProcessor} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + * <ul> + * <li>{@link #create()} - creates an empty, unbounded {@code UnicastProcessor} that + * caches all items and the terminal event it receives.</li> + * <li>{@link #create(int)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many <b>total</b> items one expects to retain.</li> + * <li>{@link #create(boolean)} - creates an empty, unbounded {@code UnicastProcessor} that + * optionally delays an error it receives and replays it after the regular items have been emitted.</li> + * <li>{@link #create(int, Runnable)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many <b>total</b> items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastProcessor} gets terminated or the single {@code Subscriber} cancels.</li> + * <li>{@link #create(int, Runnable, boolean)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many <b>total</b> items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastProcessor} gets terminated or the single {@code Subscriber} cancels + * and optionally delays an error it receives and replays it after the regular items have been emitted.</li> + * </ul> + * <p> + * If more than one {@code Subscriber} attempts to subscribe to this Processor, they + * will receive an {@link IllegalStateException} if this {@link UnicastProcessor} hasn't terminated yet, * or the Subscribers receive the terminal event (error or completion) if this * Processor has terminated. * <p> - * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/UnicastProcessor.png" alt=""> + * The {@code UnicastProcessor} buffers notifications and replays them to the single {@code Subscriber} as requested, + * for which it holds upstream items an unbounded internal buffer until they can be emitted. + * <p> + * Since a {@code UnicastProcessor} is a Reactive Streams {@code Processor}, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + * <p> + * Since a {@code UnicastProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor}, it + * honors the downstream backpressure but consumes an upstream source in an unbounded manner (requesting {@code Long.MAX_VALUE}). + * <p> + * When this {@code UnicastProcessor} is terminated via {@link #onError(Throwable)} the current or late single {@code Subscriber} + * may receive the {@code Throwable} before any available items could be emitted. To make sure an {@code onError} event is delivered + * to the {@code Subscriber} after the normal items, create a {@code UnicastProcessor} with the {@link #create(boolean)} or + * {@link #create(int, Runnable, boolean)} factory methods. + * <p> + * Even though {@code UnicastProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code UnicastProcessor} reached its terminal state will result in the + * given {@code Subscription} being canceled immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * <p> + * This {@code UnicastProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. + * <dl> + * <dt><b>Backpressure:</b></dt> + * <dd>{@code UnicastProcessor} honors the downstream backpressure but consumes an upstream source + * (if any) in an unbounded manner (requesting {@code Long.MAX_VALUE}).</dd> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code UnicastProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the single {@code Subscriber} gets notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code UnicastProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the current single {@code Subscriber}. During this emission, + * if the single {@code Subscriber}s cancels its respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * If there were no {@code Subscriber}s subscribed to this {@code UnicastProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * UnicastProcessor<Integer> processor = UnicastProcessor.create(); + * + * TestSubscriber<Integer> ts1 = processor.test(); + * + * // fresh UnicastProcessors are empty + * ts1.assertEmpty(); + * + * TestSubscriber<Integer> ts2 = processor.test(); + * + * // A UnicastProcessor only allows one Subscriber during its lifetime + * ts2.assertFailure(IllegalStateException.class); + * + * processor.onNext(1); + * ts1.assertValue(1); + * + * processor.onNext(2); + * ts1.assertValues(1, 2); + * + * processor.onComplete(); + * ts1.assertResult(1, 2); + * + * // ---------------------------------------------------- + * + * UnicastProcessor<Integer> processor2 = UnicastProcessor.create(); + * + * // a UnicastProcessor caches events until its single Subscriber subscribes + * processor2.onNext(1); + * processor2.onNext(2); + * processor2.onComplete(); + * + * TestSubscriber<Integer> ts3 = processor2.test(); + * + * // the cached events are emitted in order + * ts3.assertResult(1, 2); + * </code></pre> * * @param <T> the value type received and emitted by this Processor subclass * @since 2.0 @@ -56,7 +160,7 @@ public final class UnicastProcessor<T> extends FlowableProcessor<T> { Throwable error; - final AtomicReference<Subscriber<? super T>> actual; + final AtomicReference<Subscriber<? super T>> downstream; volatile boolean cancelled; @@ -74,6 +178,7 @@ public final class UnicastProcessor<T> extends FlowableProcessor<T> { * @return an UnicastSubject instance */ @CheckReturnValue + @NonNull public static <T> UnicastProcessor<T> create() { return new UnicastProcessor<T>(bufferSize()); } @@ -85,19 +190,21 @@ public static <T> UnicastProcessor<T> create() { * @return an UnicastProcessor instance */ @CheckReturnValue + @NonNull public static <T> UnicastProcessor<T> create(int capacityHint) { return new UnicastProcessor<T>(capacityHint); } /** * Creates an UnicastProcessor with default internal buffer capacity hint and delay error flag. + * <p>History: 2.0.8 - experimental * @param <T> the value type * @param delayError deliver pending onNext events before onError * @return an UnicastProcessor instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental + @NonNull public static <T> UnicastProcessor<T> create(boolean delayError) { return new UnicastProcessor<T>(bufferSize(), null, delayError); } @@ -115,6 +222,7 @@ public static <T> UnicastProcessor<T> create(boolean delayError) { * @return an UnicastProcessor instance */ @CheckReturnValue + @NonNull public static <T> UnicastProcessor<T> create(int capacityHint, Runnable onCancelled) { ObjectHelper.requireNonNull(onCancelled, "onTerminate"); return new UnicastProcessor<T>(capacityHint, onCancelled); @@ -126,16 +234,16 @@ public static <T> UnicastProcessor<T> create(int capacityHint, Runnable onCancel * * <p>The callback, if not null, is called exactly once and * non-overlapped with any active replay. - * + * <p>History: 2.0.8 - experimental * @param <T> the value type * @param capacityHint the hint to size the internal unbounded buffer * @param onCancelled the non null callback * @param delayError deliver pending onNext events before onError * @return an UnicastProcessor instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental + @NonNull public static <T> UnicastProcessor<T> create(int capacityHint, Runnable onCancelled, boolean delayError) { ObjectHelper.requireNonNull(onCancelled, "onTerminate"); return new UnicastProcessor<T>(capacityHint, onCancelled, delayError); @@ -147,7 +255,7 @@ public static <T> UnicastProcessor<T> create(int capacityHint, Runnable onCancel * @since 2.0 */ UnicastProcessor(int capacityHint) { - this(capacityHint,null, true); + this(capacityHint, null, true); } /** @@ -164,24 +272,25 @@ public static <T> UnicastProcessor<T> create(int capacityHint, Runnable onCancel /** * Creates an UnicastProcessor with the given capacity hint and callback * for when the Processor is terminated normally or its single Subscriber cancels. + * <p>History: 2.0.8 - experimental * @param capacityHint the capacity hint for the internal, unbounded queue * @param onTerminate the callback to run when the Processor is terminated or cancelled, null not allowed * @param delayError deliver pending onNext events before onError - * @since 2.0.8 - experimental + * @since 2.2 */ UnicastProcessor(int capacityHint, Runnable onTerminate, boolean delayError) { this.queue = new SpscLinkedArrayQueue<T>(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); this.onTerminate = new AtomicReference<Runnable>(onTerminate); this.delayError = delayError; - this.actual = new AtomicReference<Subscriber<? super T>>(); + this.downstream = new AtomicReference<Subscriber<? super T>>(); this.once = new AtomicBoolean(); this.wip = new UnicastQueueSubscription(); this.requested = new AtomicLong(); } void doTerminate() { - Runnable r = onTerminate.get(); - if (r != null && onTerminate.compareAndSet(r, null)) { + Runnable r = onTerminate.getAndSet(null); + if (r != null) { r.run(); } } @@ -238,8 +347,7 @@ void drainFused(Subscriber<? super T> a) { for (;;) { if (cancelled) { - q.clear(); - actual.lazySet(null); + downstream.lazySet(null); return; } @@ -247,14 +355,14 @@ void drainFused(Subscriber<? super T> a) { if (failFast && d && error != null) { q.clear(); - actual.lazySet(null); + downstream.lazySet(null); a.onError(error); return; } a.onNext(null); if (d) { - actual.lazySet(null); + downstream.lazySet(null); Throwable ex = error; if (ex != null) { @@ -279,7 +387,7 @@ void drain() { int missed = 1; - Subscriber<? super T> a = actual.get(); + Subscriber<? super T> a = downstream.get(); for (;;) { if (a != null) { @@ -295,27 +403,27 @@ void drain() { if (missed == 0) { break; } - a = actual.get(); + a = downstream.get(); } } boolean checkTerminated(boolean failFast, boolean d, boolean empty, Subscriber<? super T> a, SpscLinkedArrayQueue<T> q) { if (cancelled) { q.clear(); - actual.lazySet(null); + downstream.lazySet(null); return true; } if (d) { if (failFast && error != null) { q.clear(); - actual.lazySet(null); + downstream.lazySet(null); a.onError(error); return true; } if (empty) { Throwable e = error; - actual.lazySet(null); + downstream.lazySet(null); if (e != null) { a.onError(e); } else { @@ -339,12 +447,9 @@ public void onSubscribe(Subscription s) { @Override public void onNext(T t) { - if (done || cancelled) { - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + if (done || cancelled) { return; } @@ -354,15 +459,13 @@ public void onNext(T t) { @Override public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done || cancelled) { RxJavaPlugins.onError(t); return; } - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - error = t; done = true; @@ -389,9 +492,9 @@ protected void subscribeActual(Subscriber<? super T> s) { if (!once.get() && once.compareAndSet(false, true)) { s.onSubscribe(wip); - actual.set(s); + downstream.set(s); if (cancelled) { - actual.lazySet(null); + downstream.lazySet(null); } else { drain(); } @@ -402,7 +505,6 @@ protected void subscribeActual(Subscriber<? super T> s) { final class UnicastQueueSubscription extends BasicIntQueueSubscription<T> { - private static final long serialVersionUID = -4896760517184205454L; @Nullable @@ -447,10 +549,11 @@ public void cancel() { doTerminate(); - if (!enableOperatorFusion) { - if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (!enableOperatorFusion) { queue.clear(); - actual.lazySet(null); } } } @@ -458,10 +561,11 @@ public void cancel() { @Override public boolean hasSubscribers() { - return actual.get() != null; + return downstream.get() != null; } @Override + @Nullable public Throwable getThrowable() { if (done) { return error; diff --git a/src/main/java/io/reactivex/processors/package-info.java b/src/main/java/io/reactivex/processors/package-info.java index 64caf9a4c4..b6a619372f 100644 --- a/src/main/java/io/reactivex/processors/package-info.java +++ b/src/main/java/io/reactivex/processors/package-info.java @@ -15,7 +15,25 @@ */ /** - * Classes extending the Flowable base reactive class and implementing - * the Subscriber interface at the same time (aka hot Flowables). + * Classes representing so-called hot backpressure-aware sources, aka <strong>processors</strong>, + * that implement the {@link io.reactivex.processors.FlowableProcessor FlowableProcessor} class, + * the Reactive Streams {@link org.reactivestreams.Processor Processor} interface + * to allow forms of multicasting events to one or more subscribers as well as consuming another + * Reactive Streams {@link org.reactivestreams.Publisher Publisher}. + * <p> + * Available processor implementations: + * <br> + * <ul> + * <li>{@link io.reactivex.processors.AsyncProcessor AsyncProcessor} - replays the very last item</li> + * <li>{@link io.reactivex.processors.BehaviorProcessor BehaviorProcessor} - remembers the latest item</li> + * <li>{@link io.reactivex.processors.MulticastProcessor MulticastProcessor} - coordinates its source with its consumers</li> + * <li>{@link io.reactivex.processors.PublishProcessor PublishProcessor} - dispatches items to current consumers</li> + * <li>{@link io.reactivex.processors.ReplayProcessor ReplayProcessor} - remembers some or all items and replays them to consumers</li> + * <li>{@link io.reactivex.processors.UnicastProcessor UnicastProcessor} - remembers or relays items to a single consumer</li> + * </ul> + * <p> + * The non-backpressured variants of the {@code FlowableProcessor} class are called + * {@link io.reactivex.subjects.Subject}s and reside in the {@code io.reactivex.subjects} package. + * @see io.reactivex.subjects */ package io.reactivex.processors; diff --git a/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java b/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java new file mode 100644 index 0000000000..aa930e4f97 --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.schedulers; + +import io.reactivex.annotations.*; +import io.reactivex.functions.Function; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Interface to indicate the implementor class wraps a {@code Runnable} that can + * be accessed via {@link #getWrappedRunnable()}. + * <p> + * You can check if a {@link Runnable} task submitted to a {@link io.reactivex.Scheduler Scheduler} (or its + * {@link io.reactivex.Scheduler.Worker Scheduler.Worker}) implements this interface and unwrap the + * original {@code Runnable} instance. This could help to avoid hooking the same underlying {@code Runnable} + * task in a custom {@link RxJavaPlugins#onSchedule(Runnable)} hook set via + * the {@link RxJavaPlugins#setScheduleHandler(Function)} method multiple times due to internal delegation + * of the default {@code Scheduler.scheduleDirect} or {@code Scheduler.Worker.schedule} methods. + * <p>History: 2.1.7 - experimental + * @since 2.2 + */ +public interface SchedulerRunnableIntrospection { + + /** + * Returns the wrapped action. + * + * @return the wrapped action. Cannot be null. + */ + @NonNull + Runnable getWrappedRunnable(); +} diff --git a/src/main/java/io/reactivex/schedulers/Schedulers.java b/src/main/java/io/reactivex/schedulers/Schedulers.java index e7ec90bd57..9e070690b8 100644 --- a/src/main/java/io/reactivex/schedulers/Schedulers.java +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -13,13 +13,13 @@ package io.reactivex.schedulers; +import java.util.concurrent.*; + import io.reactivex.Scheduler; -import io.reactivex.annotations.NonNull; +import io.reactivex.annotations.*; import io.reactivex.internal.schedulers.*; import io.reactivex.plugins.RxJavaPlugins; -import java.util.concurrent.*; - /** * Static factory methods for returning standard Scheduler instances. * <p> @@ -29,6 +29,7 @@ * <p> * <strong>Supported system properties ({@code System.getProperty()}):</strong> * <ul> + * <li>{@code rx2.io-keep-alive-time} (long): sets the keep-alive time of the {@link #io()} Scheduler workers, default is {@link IoScheduler#KEEP_ALIVE_TIME_DEFAULT}</li> * <li>{@code rx2.io-priority} (int): sets the thread priority of the {@link #io()} Scheduler, default is {@link Thread#NORM_PRIORITY}</li> * <li>{@code rx2.computation-threads} (int): sets the number of threads in the {@link #computation()} Scheduler, default is the number of available CPUs</li> * <li>{@code rx2.computation-priority} (int): sets the thread priority of the {@link #computation()} Scheduler, default is {@link Thread#NORM_PRIORITY}</li> @@ -140,10 +141,10 @@ public static Scheduler computation() { * <p> * This can be used for asynchronously performing blocking IO. * <p> - * The implementation is backed by a pool of single-threaded {link ScheduledExecutorService} instances - * that will try to reuse previoulsy started instances used by the worker + * The implementation is backed by a pool of single-threaded {@link ScheduledExecutorService} instances + * that will try to reuse previously started instances used by the worker * returned by {@link io.reactivex.Scheduler#createWorker()} but otherwise will start a new backing - * {link ScheduledExecutorService} instance. Note that this scheduler may create an unbounded number + * {@link ScheduledExecutorService} instance. Note that this scheduler may create an unbounded number * of worker threads that can result in system slowdowns or {@code OutOfMemoryError}. Therefore, for casual uses * or when implementing an operator, the Worker instances must be disposed via {@link io.reactivex.Scheduler.Worker#dispose()}. * <p> @@ -155,6 +156,7 @@ public static Scheduler computation() { * before the {@link Schedulers} class is referenced in your code. * <p><strong>Supported system properties ({@code System.getProperty()}):</strong> * <ul> + * <li>{@code rx2.io-keep-alive-time} (long): sets the keep-alive time of the {@link #io()} Scheduler workers, default is {@link IoScheduler#KEEP_ALIVE_TIME_DEFAULT}</li> * <li>{@code rx2.io-priority} (int): sets the thread priority of the {@link #io()} Scheduler, default is {@link Thread#NORM_PRIORITY}</li> * </ul> * <p> @@ -243,9 +245,9 @@ public static Scheduler newThread() { * <p> * Uses: * <ul> - * <li>main event loop</li> + * <li>event loop</li> * <li>support Schedulers.from(Executor) and from(ExecutorService) with delayed scheduling</li> - * <li>support benchmarks that pipeline data from the main thread to some other thread and + * <li>support benchmarks that pipeline data from some thread to another thread and * avoid core-bashing of computation's round-robin nature</li> * </ul> * <p> @@ -297,6 +299,9 @@ public static Scheduler single() { * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting * before posting the actual task to the given executor. * <p> + * Tasks submitted to the {@link io.reactivex.Scheduler.Worker Scheduler.Worker} of this {@code Scheduler} are also not interruptible. Use the + * {@link #from(Executor, boolean)} overload to enable task interruption via this wrapper. + * <p> * If the provided executor supports the standard Java {@link ExecutorService} API, * cancelling tasks scheduled by this scheduler can be cancelled/interrupted by calling * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with @@ -327,7 +332,7 @@ public static Scheduler single() { * } * </code></pre> * <p> - * This type of scheduler is less sensitive to leaking {@link io.reactivex.Scheduler.Worker} instances, although + * This type of scheduler is less sensitive to leaking {@link io.reactivex.Scheduler.Worker Scheduler.Worker} instances, although * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or * execute those tasks "unexpectedly". * <p> @@ -338,7 +343,67 @@ public static Scheduler single() { */ @NonNull public static Scheduler from(@NonNull Executor executor) { - return new ExecutorScheduler(executor); + return new ExecutorScheduler(executor, false); + } + + /** + * Wraps an {@link Executor} into a new Scheduler instance and delegates {@code schedule()} + * calls to it. + * <p> + * The tasks scheduled by the returned {@link Scheduler} and its {@link io.reactivex.Scheduler.Worker Scheduler.Worker} + * can be optionally interrupted. + * <p> + * If the provided executor doesn't support any of the more specific standard Java executor + * APIs, tasks scheduled with a time delay or periodically will use the + * {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + * <p> + * If the provided executor supports the standard Java {@link ExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + * <p> + * If the provided executor supports the standard Java {@link ScheduledExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the provided executor. Note, however, if the provided + * {@code ScheduledExecutorService} instance is not single threaded, tasks scheduled + * with a time delay close to each other may end up executing in different order than + * the original schedule() call was issued. This limitation may be lifted in a future patch. + * <p> + * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided + * executor's lifecycle must be managed externally: + * <pre><code> + * ExecutorService exec = Executors.newSingleThreadedExecutor(); + * try { + * Scheduler scheduler = Schedulers.from(exec, true); + * Flowable.just(1) + * .subscribeOn(scheduler) + * .map(v -> v + 1) + * .observeOn(scheduler) + * .blockingSubscribe(System.out::println); + * } finally { + * exec.shutdown(); + * } + * </code></pre> + * <p> + * This type of scheduler is less sensitive to leaking {@link io.reactivex.Scheduler.Worker Scheduler.Worker} instances, although + * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or + * execute those tasks "unexpectedly". + * <p> + * Note that this method returns a new {@link Scheduler} instance, even for the same {@link Executor} instance. + * @param executor + * the executor to wrap + * @param interruptibleWorker if {@code true} the tasks submitted to the {@link io.reactivex.Scheduler.Worker Scheduler.Worker} will + * be interrupted when the task is disposed. + * @return the new Scheduler wrapping the Executor + * @since 2.2.6 - experimental + */ + @NonNull + @Experimental + public static Scheduler from(@NonNull Executor executor, boolean interruptibleWorker) { + return new ExecutorScheduler(executor, interruptibleWorker); } /** diff --git a/src/main/java/io/reactivex/schedulers/TestScheduler.java b/src/main/java/io/reactivex/schedulers/TestScheduler.java index 929fa22432..44272ffc25 100644 --- a/src/main/java/io/reactivex/schedulers/TestScheduler.java +++ b/src/main/java/io/reactivex/schedulers/TestScheduler.java @@ -35,6 +35,25 @@ public final class TestScheduler extends Scheduler { // Storing time in nanoseconds internally. volatile long time; + /** + * Creates a new TestScheduler with initial virtual time of zero. + */ + public TestScheduler() { + // No-op. + } + + /** + * Creates a new TestScheduler with the specified initial virtual time. + * + * @param delayTime + * the point in time to move the Scheduler's clock to + * @param unit + * the units of time that {@code delayTime} is expressed in + */ + public TestScheduler(long delayTime, TimeUnit unit) { + time = unit.toNanos(delayTime); + } + static final class TimedRunnable implements Comparable<TimedRunnable> { final long time; @@ -102,14 +121,14 @@ public void triggerActions() { } private void triggerActions(long targetTimeInNanoseconds) { - while (!queue.isEmpty()) { + for (;;) { TimedRunnable current = queue.peek(); - if (current.time > targetTimeInNanoseconds) { + if (current == null || current.time > targetTimeInNanoseconds) { break; } // if scheduled time is 0 (immediate) use current virtual time time = current.time == 0 ? time : current.time; - queue.remove(); + queue.remove(current); // Only execute if not unsubscribed if (!current.scheduler.disposed) { diff --git a/src/main/java/io/reactivex/schedulers/package-info.java b/src/main/java/io/reactivex/schedulers/package-info.java index 431ca3e8e5..7ba9d63567 100644 --- a/src/main/java/io/reactivex/schedulers/package-info.java +++ b/src/main/java/io/reactivex/schedulers/package-info.java @@ -14,7 +14,9 @@ * limitations under the License. */ /** - * Scheduler implementations, value+time record class and the standard factory class to - * return standard RxJava schedulers or wrap any Executor-based (thread pool) instances. + * Contains notably the factory class of {@link io.reactivex.schedulers.Schedulers Schedulers} providing methods for + * retrieving the standard scheduler instances, the {@link io.reactivex.schedulers.TestScheduler TestScheduler} for testing flows + * with scheduling in a controlled manner and the class {@link io.reactivex.schedulers.Timed Timed} that can hold + * a value and a timestamp associated with it. */ package io.reactivex.schedulers; diff --git a/src/main/java/io/reactivex/subjects/AsyncSubject.java b/src/main/java/io/reactivex/subjects/AsyncSubject.java index 9b9cf0d423..ce9b2dcedb 100644 --- a/src/main/java/io/reactivex/subjects/AsyncSubject.java +++ b/src/main/java/io/reactivex/subjects/AsyncSubject.java @@ -13,24 +13,101 @@ package io.reactivex.subjects; -import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; import io.reactivex.Observer; +import io.reactivex.annotations.CheckReturnValue; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.observers.DeferredScalarDisposable; import io.reactivex.plugins.RxJavaPlugins; /** - * An Subject that emits the very last value followed by a completion event or the received error to Observers. - * - * <p>The implementation of onXXX methods are technically thread-safe but non-serialized calls + * A Subject that emits the very last value followed by a completion event or the received error to Observers. + * <p> + * <img width="640" height="239" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/AsyncSubject.png" alt=""> + * <p> + * This subject does not have a public constructor by design; a new empty instance of this + * {@code AsyncSubject} can be created via the {@link #create()} method. + * <p> + * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * as parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since an {@code AsyncSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + * <p> + * When this {@code AsyncSubject} is terminated via {@link #onError(Throwable)}, the + * last observed item (if any) is cleared and late {@link io.reactivex.Observer}s only receive + * the {@code onError} event. + * <p> + * The {@code AsyncSubject} caches the latest item internally and it emits this item only when {@code onComplete} is called. + * Therefore, it is not recommended to use this {@code Subject} with infinite or never-completing sources. + * <p> + * Even though {@code AsyncSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code AsyncSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * The implementation of onXXX methods are technically thread-safe but non-serialized calls * to them may lead to undefined state in the currently subscribed Observers. + * <p> + * This {@code AsyncSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the very last observed value - + * after this {@code AsyncSubject} has been completed - in a non-blocking and thread-safe + * manner via {@link #hasValue()}, {@link #getValue()}, {@link #getValues()} or {@link #getValues(Object[])}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code AsyncSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code AsyncSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code AsyncSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * AsyncSubject<Object> subject = AsyncSubject.create(); + * + * TestObserver<Object> to1 = subject.test(); + * + * to1.assertEmpty(); + * + * subject.onNext(1); * + * // AsyncSubject only emits when onComplete was called. + * to1.assertEmpty(); + * + * subject.onNext(2); + * subject.onComplete(); + * + * // onComplete triggers the emission of the last cached item and the onComplete event. + * to1.assertResult(2); + * + * TestObserver<Object> to2 = subject.test(); + * + * // late Observers receive the last cached item too + * to2.assertResult(2); + * </code></pre> * @param <T> the value type */ - public final class AsyncSubject<T> extends Subject<T> { @SuppressWarnings("rawtypes") @@ -53,6 +130,7 @@ public final class AsyncSubject<T> extends Subject<T> { * @return the new AsyncProcessor instance */ @CheckReturnValue + @NonNull public static <T> AsyncSubject<T> create() { return new AsyncSubject<T>(); } @@ -67,40 +145,25 @@ public static <T> AsyncSubject<T> create() { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { if (subscribers.get() == TERMINATED) { - s.dispose(); + d.dispose(); } } @Override public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { return; } - if (t == null) { - nullOnNext(); - return; - } value = t; } - @SuppressWarnings("unchecked") - void nullOnNext() { - value = null; - Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - error = ex; - for (AsyncDisposable<T> as : subscribers.getAndSet(TERMINATED)) { - as.onError(ex); - } - } - @SuppressWarnings("unchecked") @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { RxJavaPlugins.onError(t); return; @@ -152,9 +215,9 @@ public Throwable getThrowable() { } @Override - protected void subscribeActual(Observer<? super T> s) { - AsyncDisposable<T> as = new AsyncDisposable<T>(s, this); - s.onSubscribe(as); + protected void subscribeActual(Observer<? super T> observer) { + AsyncDisposable<T> as = new AsyncDisposable<T>(observer, this); + observer.onSubscribe(as); if (add(as)) { if (as.isDisposed()) { remove(as); @@ -162,7 +225,7 @@ protected void subscribeActual(Observer<? super T> s) { } else { Throwable ex = error; if (ex != null) { - s.onError(ex); + observer.onError(ex); } else { T v = value; if (v != null) { @@ -253,6 +316,7 @@ public boolean hasValue() { * <p>The method is thread-safe. * @return a single value the Subject currently has or null if no such value exists */ + @Nullable public T getValue() { return subscribers.get() == TERMINATED ? value : null; } @@ -261,7 +325,9 @@ public T getValue() { * Returns an Object array containing snapshot all values of the Subject. * <p>The method is thread-safe. * @return the array containing the snapshot of all values of the Subject + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public Object[] getValues() { T v = getValue(); return v != null ? new Object[] { v } : new Object[0]; @@ -274,7 +340,9 @@ public Object[] getValues() { * <p>The method is thread-safe. * @param array the target array to copy values into if it fits * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public T[] getValues(T[] array) { T v = getValue(); if (v == null) { @@ -312,7 +380,7 @@ public void dispose() { void onComplete() { if (!isDisposed()) { - actual.onComplete(); + downstream.onComplete(); } } @@ -320,7 +388,7 @@ void onError(Throwable t) { if (isDisposed()) { RxJavaPlugins.onError(t); } else { - actual.onError(t); + downstream.onError(t); } } } diff --git a/src/main/java/io/reactivex/subjects/BehaviorSubject.java b/src/main/java/io/reactivex/subjects/BehaviorSubject.java index 196ad8301f..c58cb40779 100644 --- a/src/main/java/io/reactivex/subjects/BehaviorSubject.java +++ b/src/main/java/io/reactivex/subjects/BehaviorSubject.java @@ -14,6 +14,8 @@ package io.reactivex.subjects; import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; import java.lang.reflect.Array; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.*; @@ -31,8 +33,89 @@ * <p> * <img width="640" height="415" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.BehaviorSubject.png" alt=""> * <p> - * Example usage: + * This subject does not have a public constructor by design; a new empty instance of this + * {@code BehaviorSubject} can be created via the {@link #create()} method and + * a new non-empty instance can be created via {@link #createDefault(Object)} (named as such to avoid + * overload resolution conflict with {@code Observable.create} that creates an Observable, not a {@code BehaviorSubject}). + * <p> + * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * default initial values in {@link #createDefault(Object)} or as parameters to {@link #onNext(Object)} and + * {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code BehaviorSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + * <p> + * When this {@code BehaviorSubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, the + * last observed item (if any) is cleared and late {@link io.reactivex.Observer}s only receive + * the respective terminal event. + * <p> + * The {@code BehaviorSubject} does not support clearing its cached value (to appear empty again), however, the + * effect can be achieved by using a special item and making sure {@code Observer}s subscribe through a + * filter whose predicate filters out this special item: + * <pre><code> + * BehaviorSubject<Integer> subject = BehaviorSubject.create(); + * + * final Integer EMPTY = Integer.MIN_VALUE; + * + * Observable<Integer> observable = subject.filter(v -> v != EMPTY); + * + * TestObserver<Integer> to1 = observable.test(); + * + * observable.onNext(1); + * // this will "clear" the cache + * observable.onNext(EMPTY); + * + * TestObserver<Integer> to2 = observable.test(); + * + * subject.onNext(2); + * subject.onComplete(); + * + * // to1 received both non-empty items + * to1.assertResult(1, 2); + * + * // to2 received only 2 even though the current item was EMPTY + * // when it got subscribed + * to2.assertResult(2); + * + * // Observers coming after the subject was terminated receive + * // no items and only the onComplete event in this case. + * observable.test().assertResult(); + * </code></pre> * <p> + * Even though {@code BehaviorSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code BehaviorSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * <p> + * This {@code BehaviorSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the latest observed value + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code BehaviorSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code BehaviorSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code BehaviorSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: * <pre> {@code // observer will receive all 4 events (including "default"). @@ -98,6 +181,7 @@ public final class BehaviorSubject<T> extends Subject<T> { * @return the constructed {@link BehaviorSubject} */ @CheckReturnValue + @NonNull public static <T> BehaviorSubject<T> create() { return new BehaviorSubject<T>(); } @@ -114,6 +198,7 @@ public static <T> BehaviorSubject<T> create() { * @return the constructed {@link BehaviorSubject} */ @CheckReturnValue + @NonNull public static <T> BehaviorSubject<T> createDefault(T defaultValue) { return new BehaviorSubject<T>(defaultValue); } @@ -164,18 +249,16 @@ protected void subscribeActual(Observer<? super T> observer) { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { if (terminalEvent.get() != null) { - s.dispose(); + d.dispose(); } } @Override public void onNext(T t) { - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (terminalEvent.get() != null) { return; } @@ -188,9 +271,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (!terminalEvent.compareAndSet(null, t)) { RxJavaPlugins.onError(t); return; @@ -217,12 +298,12 @@ public boolean hasObservers() { return subscribers.get().length != 0; } - /* test support*/ int subscriberCount() { return subscribers.get().length; } @Override + @Nullable public Throwable getThrowable() { Object o = value.get(); if (NotificationLite.isError(o)) { @@ -236,6 +317,7 @@ public Throwable getThrowable() { * <p>The method is thread-safe. * @return a single value the Subject currently has or null if no such value exists */ + @Nullable public T getValue() { Object o = value.get(); if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { @@ -248,7 +330,9 @@ public T getValue() { * Returns an Object array containing snapshot all values of the Subject. * <p>The method is thread-safe. * @return the array containing the snapshot of all values of the Subject + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated public Object[] getValues() { @SuppressWarnings("unchecked") T[] a = (T[])EMPTY_ARRAY; @@ -267,7 +351,9 @@ public Object[] getValues() { * <p>The method is thread-safe. * @param array the target array to copy values into if it fits * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x */ + @Deprecated @SuppressWarnings("unchecked") public T[] getValues(T[] array) { Object o = value.get(); @@ -333,10 +419,10 @@ boolean add(BehaviorDisposable<T> rs) { void remove(BehaviorDisposable<T> rs) { for (;;) { BehaviorDisposable<T>[] a = subscribers.get(); - if (a == TERMINATED || a == EMPTY) { + int len = a.length; + if (len == 0) { return; } - int len = a.length; int j = -1; for (int i = 0; i < len; i++) { if (a[i] == rs) { @@ -365,13 +451,10 @@ void remove(BehaviorDisposable<T> rs) { @SuppressWarnings("unchecked") BehaviorDisposable<T>[] terminate(Object terminalValue) { - BehaviorDisposable<T>[] a = subscribers.get(); + BehaviorDisposable<T>[] a = subscribers.getAndSet(TERMINATED); if (a != TERMINATED) { - a = subscribers.getAndSet(TERMINATED); - if (a != TERMINATED) { - // either this or atomics with lots of allocation - setCurrent(terminalValue); - } + // either this or atomics with lots of allocation + setCurrent(terminalValue); } return a; @@ -379,17 +462,14 @@ BehaviorDisposable<T>[] terminate(Object terminalValue) { void setCurrent(Object o) { writeLock.lock(); - try { - index++; - value.lazySet(o); - } finally { - writeLock.unlock(); - } + index++; + value.lazySet(o); + writeLock.unlock(); } static final class BehaviorDisposable<T> implements Disposable, NonThrowingPredicate<Object> { - final Observer<? super T> actual; + final Observer<? super T> downstream; final BehaviorSubject<T> state; boolean next; @@ -403,7 +483,7 @@ static final class BehaviorDisposable<T> implements Disposable, NonThrowingPredi long index; BehaviorDisposable(Observer<? super T> actual, BehaviorSubject<T> state) { - this.actual = actual; + this.downstream = actual; this.state = state; } @@ -486,7 +566,7 @@ void emitNext(Object value, long stateIndex) { @Override public boolean test(Object o) { - return cancelled || NotificationLite.accept(o, actual); + return cancelled || NotificationLite.accept(o, downstream); } void emitLoop() { diff --git a/src/main/java/io/reactivex/subjects/CompletableSubject.java b/src/main/java/io/reactivex/subjects/CompletableSubject.java index dad98fc11d..328e35175f 100644 --- a/src/main/java/io/reactivex/subjects/CompletableSubject.java +++ b/src/main/java/io/reactivex/subjects/CompletableSubject.java @@ -13,22 +13,76 @@ package io.reactivex.subjects; +import io.reactivex.annotations.Nullable; import java.util.concurrent.atomic.*; import io.reactivex.*; -import io.reactivex.annotations.*; +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; /** * Represents a hot Completable-like source and consumer of events similar to Subjects. * <p> - * All methods are thread safe. Calling onComplete multiple - * times has no effect. Calling onError multiple times relays the Throwable to - * the RxJavaPlugins' error handler. + * <img width="640" height="243" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/CompletableSubject.png" alt=""> * <p> - * The CompletableSubject doesn't store the Disposables coming through onSubscribe but - * disposes them once the other onXXX methods were called (terminal state reached). + * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code CompletableSubject} can be created via the {@link #create()} method. + * <p> + * Since the {@code CompletableSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * as parameters to {@link #onError(Throwable)}. + * <p> + * Even though {@code CompletableSubject} implements the {@code CompletableObserver} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code CompletableSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * All methods are thread safe. Calling {@link #onComplete()} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} global error handler. + * <p> + * This {@code CompletableSubject} supports the standard state-peeking methods {@link #hasComplete()}, + * {@link #hasThrowable()}, {@link #getThrowable()} and {@link #hasObservers()}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code CompletableSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code CompletableObserver}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code CompletableSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code CompletableObserver}s. During this emission, + * if one or more {@code CompletableObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code CompletableObserver}s + * cancel at once). + * If there were no {@code CompletableObserver}s subscribed to this {@code CompletableSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * CompletableSubject subject = CompletableSubject.create(); + * + * TestObserver<Void> to1 = subject.test(); + * + * // a fresh CompletableSubject is empty + * to1.assertEmpty(); + * + * subject.onComplete(); + * + * // a CompletableSubject is always void of items + * to1.assertResult(); + * + * TestObserver<Void> to2 = subject.test() + * + * // late CompletableObservers receive the terminal event + * to2.assertResult(); + * </code></pre> * <p>History: 2.0.5 - experimental * @since 2.1 */ @@ -48,6 +102,7 @@ public final class CompletableSubject extends Completable implements Completable * @return the new CompletableSubject instance */ @CheckReturnValue + @NonNull public static CompletableSubject create() { return new CompletableSubject(); } @@ -66,13 +121,11 @@ public void onSubscribe(Disposable d) { @Override public void onError(Throwable e) { - if (e == null) { - e = new NullPointerException("Null errors are not allowed in 2.x"); - } + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (once.compareAndSet(false, true)) { this.error = e; for (CompletableDisposable md : observers.getAndSet(TERMINATED)) { - md.actual.onError(e); + md.downstream.onError(e); } } else { RxJavaPlugins.onError(e); @@ -83,7 +136,7 @@ public void onError(Throwable e) { public void onComplete() { if (once.compareAndSet(false, true)) { for (CompletableDisposable md : observers.getAndSet(TERMINATED)) { - md.actual.onComplete(); + md.downstream.onComplete(); } } } @@ -163,6 +216,7 @@ void remove(CompletableDisposable inner) { * Returns the terminal error if this CompletableSubject has been terminated with an error, null otherwise. * @return the terminal error or null if not terminated or not with an error */ + @Nullable public Throwable getThrowable() { if (observers.get() == TERMINATED) { return error; @@ -206,10 +260,10 @@ static final class CompletableDisposable extends AtomicReference<CompletableSubject> implements Disposable { private static final long serialVersionUID = -7650903191002190468L; - final CompletableObserver actual; + final CompletableObserver downstream; CompletableDisposable(CompletableObserver actual, CompletableSubject parent) { - this.actual = actual; + this.downstream = actual; lazySet(parent); } diff --git a/src/main/java/io/reactivex/subjects/MaybeSubject.java b/src/main/java/io/reactivex/subjects/MaybeSubject.java index c1cda23955..ef9128c4f6 100644 --- a/src/main/java/io/reactivex/subjects/MaybeSubject.java +++ b/src/main/java/io/reactivex/subjects/MaybeSubject.java @@ -18,17 +18,93 @@ import io.reactivex.*; import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; /** * Represents a hot Maybe-like source and consumer of events similar to Subjects. * <p> - * All methods are thread safe. Calling onSuccess or onComplete multiple - * times has no effect. Calling onError multiple times relays the Throwable to - * the RxJavaPlugins' error handler. + * <img width="640" height="164" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/MaybeSubject.png" alt=""> * <p> - * The MaybeSubject doesn't store the Disposables coming through onSubscribe but - * disposes them once the other onXXX methods were called (terminal state reached). + * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code MaybeSubject} can be created via the {@link #create()} method. + * <p> + * Since the {@code MaybeSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * as parameters to {@link #onSuccess(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code MaybeSubject} is a {@link io.reactivex.Maybe}, calling {@code onSuccess}, {@code onError} + * or {@code onComplete} will move this {@code MaybeSubject} into its terminal state atomically. + * <p> + * All methods are thread safe. Calling {@link #onSuccess(Object)} or {@link #onComplete()} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} global error handler. + * <p> + * Even though {@code MaybeSubject} implements the {@code MaybeObserver} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code MaybeSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * This {@code MaybeSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read any success item in a non-blocking + * and thread-safe manner via {@link #hasValue()} and {@link #getValue()}. + * <p> + * The {@code MaybeSubject} does not support clearing its cached {@code onSuccess} value. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code MaybeSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code MaybeObserver}s get notified on the thread where the terminating {@code onSuccess}, {@code onError} or {@code onComplete} + * methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code MaybeSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code MaybeObserver}s. During this emission, + * if one or more {@code MaybeObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code MaybeObserver}s + * cancel at once). + * If there were no {@code MaybeObserver}s subscribed to this {@code MaybeSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * MaybeSubject<Integer> subject1 = MaybeSubject.create(); + * + * TestObserver<Integer> to1 = subject1.test(); + * + * // MaybeSubjects are empty by default + * to1.assertEmpty(); + * + * subject1.onSuccess(1); + * + * // onSuccess is a terminal event with MaybeSubjects + * // TestObserver converts onSuccess into onNext + onComplete + * to1.assertResult(1); + * + * TestObserver<Integer> to2 = subject1.test(); + * + * // late Observers receive the terminal signal (onSuccess) too + * to2.assertResult(1); + * + * // ----------------------------------------------------- + * + * MaybeSubject<Integer> subject2 = MaybeSubject.create(); + * + * TestObserver<Integer> to3 = subject2.test(); + * + * subject2.onComplete(); + * + * // a completed MaybeSubject completes its MaybeObservers + * to3.assertResult(); + * + * TestObserver<Integer> to4 = subject1.test(); + * + * // late Observers receive the terminal signal (onComplete) too + * to4.assertResult(); + * </code></pre> * <p>History: 2.0.5 - experimental * @param <T> the value type received and emitted * @since 2.1 @@ -53,6 +129,7 @@ public final class MaybeSubject<T> extends Maybe<T> implements MaybeObserver<T> * @return the new MaybeSubject instance */ @CheckReturnValue + @NonNull public static <T> MaybeSubject<T> create() { return new MaybeSubject<T>(); } @@ -73,14 +150,11 @@ public void onSubscribe(Disposable d) { @SuppressWarnings("unchecked") @Override public void onSuccess(T value) { - if (value == null) { - onError(new NullPointerException("Null values are not allowed in 2.x")); - return; - } + ObjectHelper.requireNonNull(value, "onSuccess called with null. Null values are generally not allowed in 2.x operators and sources."); if (once.compareAndSet(false, true)) { this.value = value; for (MaybeDisposable<T> md : observers.getAndSet(TERMINATED)) { - md.actual.onSuccess(value); + md.downstream.onSuccess(value); } } } @@ -88,13 +162,11 @@ public void onSuccess(T value) { @SuppressWarnings("unchecked") @Override public void onError(Throwable e) { - if (e == null) { - e = new NullPointerException("Null errors are not allowed in 2.x"); - } + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (once.compareAndSet(false, true)) { this.error = e; for (MaybeDisposable<T> md : observers.getAndSet(TERMINATED)) { - md.actual.onError(e); + md.downstream.onError(e); } } else { RxJavaPlugins.onError(e); @@ -106,7 +178,7 @@ public void onError(Throwable e) { public void onComplete() { if (once.compareAndSet(false, true)) { for (MaybeDisposable<T> md : observers.getAndSet(TERMINATED)) { - md.actual.onComplete(); + md.downstream.onComplete(); } } } @@ -192,6 +264,7 @@ void remove(MaybeDisposable<T> inner) { * Returns the success value if this MaybeSubject was terminated with a success value. * @return the success value or null */ + @Nullable public T getValue() { if (observers.get() == TERMINATED) { return value; @@ -211,6 +284,7 @@ public boolean hasValue() { * Returns the terminal error if this MaybeSubject has been terminated with an error, null otherwise. * @return the terminal error or null if not terminated or not with an error */ + @Nullable public Throwable getThrowable() { if (observers.get() == TERMINATED) { return error; @@ -254,10 +328,10 @@ static final class MaybeDisposable<T> extends AtomicReference<MaybeSubject<T>> implements Disposable { private static final long serialVersionUID = -7650903191002190468L; - final MaybeObserver<? super T> actual; + final MaybeObserver<? super T> downstream; MaybeDisposable(MaybeObserver<? super T> actual, MaybeSubject<T> parent) { - this.actual = actual; + this.downstream = actual; lazySet(parent); } diff --git a/src/main/java/io/reactivex/subjects/PublishSubject.java b/src/main/java/io/reactivex/subjects/PublishSubject.java index 297e6e6363..f59ab36754 100644 --- a/src/main/java/io/reactivex/subjects/PublishSubject.java +++ b/src/main/java/io/reactivex/subjects/PublishSubject.java @@ -14,20 +14,68 @@ package io.reactivex.subjects; import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; import java.util.concurrent.atomic.*; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; /** - * Subject that, once an {@link Observer} has subscribed, emits all subsequently observed items to the - * subscriber. + * A Subject that emits (multicasts) items to currently subscribed {@link Observer}s and terminal events to current + * or late {@code Observer}s. * <p> - * <img width="640" height="405" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.PublishSubject.png" alt=""> + * <img width="640" height="281" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/PublishSubject.png" alt=""> * <p> - * Example usage: + * This subject does not have a public constructor by design; a new empty instance of this + * {@code PublishSubject} can be created via the {@link #create()} method. + * <p> + * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code PublishSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + * <p> + * When this {@code PublishSubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link io.reactivex.Observer}s only receive the respective terminal event. + * <p> + * Unlike a {@link BehaviorSubject}, a {@code PublishSubject} doesn't retain/cache items, therefore, a new + * {@code Observer} won't receive any past items. + * <p> + * Even though {@code PublishSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code PublishSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * <p> + * This {@code PublishSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code PublishSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code PublishSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code PublishSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> * <p> + * Example usage: * <pre> {@code PublishSubject<Object> subject = PublishSubject.create(); @@ -40,6 +88,8 @@ subject.onNext("three"); subject.onComplete(); + // late Observers only receive the terminal event + subject.test().assertEmpty(); } </pre> * * @param <T> @@ -65,6 +115,7 @@ public final class PublishSubject<T> extends Subject<T> { * @return the new PublishSubject */ @CheckReturnValue + @NonNull public static <T> PublishSubject<T> create() { return new PublishSubject<T>(); } @@ -78,9 +129,8 @@ public static <T> PublishSubject<T> create() { subscribers = new AtomicReference<PublishDisposable<T>[]>(EMPTY); } - @Override - public void subscribeActual(Observer<? super T> t) { + protected void subscribeActual(Observer<? super T> t) { PublishDisposable<T> ps = new PublishDisposable<T>(t, this); t.onSubscribe(ps); if (add(ps)) { @@ -165,40 +215,32 @@ void remove(PublishDisposable<T> ps) { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { if (subscribers.get() == TERMINATED) { - s.dispose(); + d.dispose(); } } @Override public void onNext(T t) { - if (subscribers.get() == TERMINATED) { - return; - } - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } - for (PublishDisposable<T> s : subscribers.get()) { - s.onNext(t); + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + for (PublishDisposable<T> pd : subscribers.get()) { + pd.onNext(t); } } @SuppressWarnings("unchecked") @Override public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (subscribers.get() == TERMINATED) { RxJavaPlugins.onError(t); return; } - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } error = t; - for (PublishDisposable<T> s : subscribers.getAndSet(TERMINATED)) { - s.onError(t); + for (PublishDisposable<T> pd : subscribers.getAndSet(TERMINATED)) { + pd.onError(t); } } @@ -208,8 +250,8 @@ public void onComplete() { if (subscribers.get() == TERMINATED) { return; } - for (PublishDisposable<T> s : subscribers.getAndSet(TERMINATED)) { - s.onComplete(); + for (PublishDisposable<T> pd : subscribers.getAndSet(TERMINATED)) { + pd.onComplete(); } } @@ -219,6 +261,7 @@ public boolean hasObservers() { } @Override + @Nullable public Throwable getThrowable() { if (subscribers.get() == TERMINATED) { return error; @@ -246,7 +289,7 @@ static final class PublishDisposable<T> extends AtomicBoolean implements Disposa private static final long serialVersionUID = 3562861878281475070L; /** The actual subscriber. */ - final Observer<? super T> actual; + final Observer<? super T> downstream; /** The subject state. */ final PublishSubject<T> parent; @@ -256,13 +299,13 @@ static final class PublishDisposable<T> extends AtomicBoolean implements Disposa * @param parent the parent PublishProcessor */ PublishDisposable(Observer<? super T> actual, PublishSubject<T> parent) { - this.actual = actual; + this.downstream = actual; this.parent = parent; } public void onNext(T t) { if (!get()) { - actual.onNext(t); + downstream.onNext(t); } } @@ -270,13 +313,13 @@ public void onError(Throwable t) { if (get()) { RxJavaPlugins.onError(t); } else { - actual.onError(t); + downstream.onError(t); } } public void onComplete() { if (!get()) { - actual.onComplete(); + downstream.onComplete(); } } diff --git a/src/main/java/io/reactivex/subjects/ReplaySubject.java b/src/main/java/io/reactivex/subjects/ReplaySubject.java index 4b684e31bf..622854b5ec 100644 --- a/src/main/java/io/reactivex/subjects/ReplaySubject.java +++ b/src/main/java/io/reactivex/subjects/ReplaySubject.java @@ -20,22 +20,104 @@ import io.reactivex.Observer; import io.reactivex.Scheduler; -import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.util.NotificationLite; import io.reactivex.plugins.RxJavaPlugins; /** - * Replays events to Observers. + * Replays events (in a configurable bounded or unbounded manner) to current and late {@link Observer}s. * <p> - * <img width="640" height="405" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/S.ReplaySubject.png" alt=""> + * This subject does not have a public constructor by design; a new empty instance of this + * {@code ReplaySubject} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + * <ul> + * <li>{@link #create()} - creates an empty, unbounded {@code ReplaySubject} that + * caches all items and the terminal event it receives. * <p> - * Example usage: + * <img width="640" height="299" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplaySubject.u.png" alt=""> + * <p> + * <img width="640" height="398" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplaySubject.ue.png" alt=""> + * </li> + * <li>{@link #create(int)} - creates an empty, unbounded {@code ReplaySubject} + * with a hint about how many <b>total</b> items one expects to retain. + * </li> + * <li>{@link #createWithSize(int)} - creates an empty, size-bound {@code ReplaySubject} + * that retains at most the given number of the latest item it receives. + * <p> + * <img width="640" height="420" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplaySubject.n.png" alt=""> + * </li> + * <li>{@link #createWithTime(long, TimeUnit, Scheduler)} - creates an empty, time-bound + * {@code ReplaySubject} that retains items no older than the specified time amount. + * <p> + * <img width="640" height="415" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplaySubject.t.png" alt=""> + * </li> + * <li>{@link #createWithTimeAndSize(long, TimeUnit, Scheduler, int)} - creates an empty, + * time- and size-bound {@code ReplaySubject} that retains at most the given number + * items that are also not older than the specified time amount. + * <p> + * <img width="640" height="404" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/ReplaySubject.nt.png" alt=""> + * </li> + * </ul> + * <p> + * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code ReplaySubject} is an {@link io.reactivex.Observable}, it does not support backpressure. * <p> + * When this {@code ReplaySubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link io.reactivex.Observer}s will receive the retained/cached items first (if any) followed by the respective + * terminal event. If the {@code ReplaySubject} has a time-bound, the age of the retained/cached items are still considered + * when replaying and thus it may result in no items being emitted before the terminal event. + * <p> + * Once an {@code Observer} has subscribed, it will receive items continuously from that point on. Bounds only affect how + * many past items a new {@code Observer} will receive before it catches up with the live event feed. + * <p> + * Even though {@code ReplaySubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code ReplaySubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * <p> + * This {@code ReplaySubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the retained/cached items + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + * <p> + * Note that due to concurrency requirements, a size- and time-bounded {@code ReplaySubject} may hold strong references to more + * source emissions than specified while it isn't terminated yet. Use the {@link #cleanupBuffer()} to allow + * such inaccessible items to be cleaned up by GC once no consumer references it anymore. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code ReplaySubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked. + * Time-bound {@code ReplaySubject}s use the given {@code Scheduler} in their {@code create} methods + * as time source to timestamp of items received for the age checks.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code ReplaySubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code ReplaySubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: * <pre> {@code - ReplaySubject<Object> subject = new ReplaySubject<>(); + ReplaySubject<Object> subject = ReplaySubject.create(); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); @@ -76,6 +158,7 @@ public final class ReplaySubject<T> extends Subject<T> { * @return the created subject */ @CheckReturnValue + @NonNull public static <T> ReplaySubject<T> create() { return new ReplaySubject<T>(new UnboundedReplayBuffer<T>(16)); } @@ -96,6 +179,7 @@ public static <T> ReplaySubject<T> create() { * @return the created subject */ @CheckReturnValue + @NonNull public static <T> ReplaySubject<T> create(int capacityHint) { return new ReplaySubject<T>(new UnboundedReplayBuffer<T>(capacityHint)); } @@ -121,6 +205,7 @@ public static <T> ReplaySubject<T> create(int capacityHint) { * @return the created subject */ @CheckReturnValue + @NonNull public static <T> ReplaySubject<T> createWithSize(int maxSize) { return new ReplaySubject<T>(new SizeBoundReplayBuffer<T>(maxSize)); } @@ -175,6 +260,7 @@ public static <T> ReplaySubject<T> createWithSize(int maxSize) { * @return the created subject */ @CheckReturnValue + @NonNull public static <T> ReplaySubject<T> createWithTime(long maxAge, TimeUnit unit, Scheduler scheduler) { return new ReplaySubject<T>(new SizeAndTimeBoundReplayBuffer<T>(Integer.MAX_VALUE, maxAge, unit, scheduler)); } @@ -214,6 +300,7 @@ public static <T> ReplaySubject<T> createWithTime(long maxAge, TimeUnit unit, Sc * @return the created subject */ @CheckReturnValue + @NonNull public static <T> ReplaySubject<T> createWithTimeAndSize(long maxAge, TimeUnit unit, Scheduler scheduler, int maxSize) { return new ReplaySubject<T>(new SizeAndTimeBoundReplayBuffer<T>(maxSize, maxAge, unit, scheduler)); } @@ -245,18 +332,15 @@ protected void subscribeActual(Observer<? super T> observer) { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { if (done) { - s.dispose(); + d.dispose(); } } @Override public void onNext(T t) { - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); if (done) { return; } @@ -271,9 +355,7 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (done) { RxJavaPlugins.onError(t); return; @@ -319,6 +401,7 @@ public boolean hasObservers() { } @Override + @Nullable public Throwable getThrowable() { Object o = buffer.get(); if (NotificationLite.isError(o)) { @@ -332,10 +415,29 @@ public Throwable getThrowable() { * <p>The method is thread-safe. * @return a single value the Subject currently has or null if no such value exists */ + @Nullable public T getValue() { return buffer.getValue(); } + /** + * Makes sure the item cached by the head node in a bounded + * ReplaySubject is released (as it is never part of a replay). + * <p> + * By default, live bounded buffers will remember one item before + * the currently receivable one to ensure subscribers can always + * receive a continuous sequence of items. A terminated ReplaySubject + * automatically releases this inaccessible item. + * <p> + * The method must be called sequentially, similar to the standard + * {@code onXXX} methods. + * <p>History: 2.1.11 - experimental + * @since 2.2 + */ + public void cleanupBuffer() { + buffer.trimHead(); + } + /** An empty array to avoid allocation in getValues(). */ private static final Object[] EMPTY_ARRAY = new Object[0]; @@ -466,6 +568,7 @@ interface ReplayBuffer<T> { int size(); + @Nullable T getValue(); T[] getValues(T[] array); @@ -483,12 +586,18 @@ interface ReplayBuffer<T> { * @return true if successful */ boolean compareAndSet(Object expected, Object next); + + /** + * Make sure an old inaccessible head value is released + * in a bounded buffer. + */ + void trimHead(); } static final class ReplayDisposable<T> extends AtomicInteger implements Disposable { private static final long serialVersionUID = 466549804534799122L; - final Observer<? super T> actual; + final Observer<? super T> downstream; final ReplaySubject<T> state; Object index; @@ -496,7 +605,7 @@ static final class ReplayDisposable<T> extends AtomicInteger implements Disposab volatile boolean cancelled; ReplayDisposable(Observer<? super T> actual, ReplaySubject<T> state) { - this.actual = actual; + this.downstream = actual; this.state = state; } @@ -539,11 +648,18 @@ public void add(T value) { @Override public void addFinal(Object notificationLite) { buffer.add(notificationLite); + trimHead(); size++; done = true; } @Override + public void trimHead() { + // no-op in this type of buffer + } + + @Override + @Nullable @SuppressWarnings("unchecked") public T getValue() { int s = size; @@ -584,7 +700,6 @@ public T[] getValues(T[] array) { } } - if (array.length < s) { array = (T[])Array.newInstance(array.getClass().getComponentType(), s); } @@ -607,7 +722,7 @@ public void replay(ReplayDisposable<T> rs) { int missed = 1; final List<Object> b = buffer; - final Observer<? super T> a = rs.actual; + final Observer<? super T> a = rs.downstream; Integer indexObject = (Integer)rs.index; int index; @@ -758,10 +873,26 @@ public void addFinal(Object notificationLite) { size++; t.lazySet(n); // releases both the tail and size + trimHead(); done = true; } + /** + * Replace a non-empty head node with an empty one to + * allow the GC of the inaccessible old value. + */ @Override + public void trimHead() { + Node<Object> h = head; + if (h.value != null) { + Node<Object> n = new Node<Object>(null); + n.lazySet(h.get()); + head = n; + } + } + + @Override + @Nullable @SuppressWarnings("unchecked") public T getValue() { Node<Object> prev = null; @@ -825,7 +956,7 @@ public void replay(ReplayDisposable<T> rs) { } int missed = 1; - final Observer<? super T> a = rs.actual; + final Observer<? super T> a = rs.downstream; Node<Object> index = (Node<Object>)rs.index; if (index == null) { @@ -919,7 +1050,6 @@ static final class SizeAndTimeBoundReplayBuffer<T> volatile boolean done; - SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); @@ -941,6 +1071,10 @@ void trim() { TimedNode<Object> h = head; for (;;) { + if (size <= 1) { + head = h; + break; + } TimedNode<Object> next = h.get(); if (next == null) { head = h; @@ -953,6 +1087,7 @@ void trim() { } h = next; + size--; } } @@ -965,12 +1100,24 @@ void trimFinal() { for (;;) { TimedNode<Object> next = h.get(); if (next.get() == null) { - head = h; + if (h.value != null) { + TimedNode<Object> lasth = new TimedNode<Object>(null, 0L); + lasth.lazySet(h.get()); + head = lasth; + } else { + head = h; + } break; } if (next.time > limit) { - head = h; + if (h.value != null) { + TimedNode<Object> lasth = new TimedNode<Object>(null, 0L); + lasth.lazySet(h.get()); + head = lasth; + } else { + head = h; + } break; } @@ -1003,7 +1150,22 @@ public void addFinal(Object notificationLite) { done = true; } + /** + * Replace a non-empty head node with an empty one to + * allow the GC of the inaccessible old value. + */ + @Override + public void trimHead() { + TimedNode<Object> h = head; + if (h.value != null) { + TimedNode<Object> n = new TimedNode<Object>(null, 0); + n.lazySet(h.get()); + head = n; + } + } + @Override + @Nullable @SuppressWarnings("unchecked") public T getValue() { TimedNode<Object> prev = null; @@ -1088,7 +1250,7 @@ public void replay(ReplayDisposable<T> rs) { } int missed = 1; - final Observer<? super T> a = rs.actual; + final Observer<? super T> a = rs.downstream; TimedNode<Object> index = (TimedNode<Object>)rs.index; if (index == null) { diff --git a/src/main/java/io/reactivex/subjects/SerializedSubject.java b/src/main/java/io/reactivex/subjects/SerializedSubject.java index 53f3381bce..ec4263b85e 100644 --- a/src/main/java/io/reactivex/subjects/SerializedSubject.java +++ b/src/main/java/io/reactivex/subjects/SerializedSubject.java @@ -14,6 +14,7 @@ package io.reactivex.subjects; import io.reactivex.Observer; +import io.reactivex.annotations.Nullable; import io.reactivex.disposables.Disposable; import io.reactivex.internal.util.*; import io.reactivex.internal.util.AppendOnlyLinkedArrayList.NonThrowingPredicate; @@ -48,9 +49,8 @@ protected void subscribeActual(Observer<? super T> observer) { actual.subscribe(observer); } - @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { boolean cancel; if (!done) { synchronized (this) { @@ -63,7 +63,7 @@ public void onSubscribe(Disposable s) { q = new AppendOnlyLinkedArrayList<Object>(4); queue = q; } - q.add(NotificationLite.disposable(s)); + q.add(NotificationLite.disposable(d)); return; } emitting = true; @@ -74,9 +74,9 @@ public void onSubscribe(Disposable s) { cancel = true; } if (cancel) { - s.dispose(); + d.dispose(); } else { - actual.onSubscribe(s); + actual.onSubscribe(d); emitLoop(); } } @@ -193,6 +193,7 @@ public boolean hasThrowable() { } @Override + @Nullable public Throwable getThrowable() { return actual.getThrowable(); } diff --git a/src/main/java/io/reactivex/subjects/SingleSubject.java b/src/main/java/io/reactivex/subjects/SingleSubject.java index 4c486eac97..cd9e3a2cd4 100644 --- a/src/main/java/io/reactivex/subjects/SingleSubject.java +++ b/src/main/java/io/reactivex/subjects/SingleSubject.java @@ -18,17 +18,77 @@ import io.reactivex.*; import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.plugins.RxJavaPlugins; /** * Represents a hot Single-like source and consumer of events similar to Subjects. * <p> - * All methods are thread safe. Calling onSuccess multiple - * times has no effect. Calling onError multiple times relays the Throwable to - * the RxJavaPlugins' error handler. + * <img width="640" height="236" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/SingleSubject.png" alt=""> * <p> - * The SingleSubject doesn't store the Disposables coming through onSubscribe but - * disposes them once the other onXXX methods were called (terminal state reached). + * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code SingleSubject} can be created via the {@link #create()} method. + * <p> + * Since the {@code SingleSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) + * as parameters to {@link #onSuccess(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code SingleSubject} is a {@link io.reactivex.Single}, calling {@code onSuccess} or {@code onError} + * will move this {@code SingleSubject} into its terminal state atomically. + * <p> + * All methods are thread safe. Calling {@link #onSuccess(Object)} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} global error handler. + * <p> + * Even though {@code SingleSubject} implements the {@code SingleObserver} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code SingleSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * This {@code SingleSubject} supports the standard state-peeking methods {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read any success item in a non-blocking + * and thread-safe manner via {@link #hasValue()} and {@link #getValue()}. + * <p> + * The {@code SingleSubject} does not support clearing its cached {@code onSuccess} value. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code SingleSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code SingleObserver}s get notified on the thread where the terminating {@code onSuccess} or {@code onError} + * methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code SingleSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code SingleObserver}s. During this emission, + * if one or more {@code SingleObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code SingleObserver}s + * cancel at once). + * If there were no {@code SingleObserver}s subscribed to this {@code SingleSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * SingleSubject<Integer> subject1 = SingleSubject.create(); + * + * TestObserver<Integer> to1 = subject1.test(); + * + * // SingleSubjects are empty by default + * to1.assertEmpty(); + * + * subject1.onSuccess(1); + * + * // onSuccess is a terminal event with SingleSubjects + * // TestObserver converts onSuccess into onNext + onComplete + * to1.assertResult(1); + * + * TestObserver<Integer> to2 = subject1.test(); + * + * // late Observers receive the terminal signal (onSuccess) too + * to2.assertResult(1); + * </code></pre> * <p>History: 2.0.5 - experimental * @param <T> the value type received and emitted * @since 2.1 @@ -74,14 +134,11 @@ public void onSubscribe(@NonNull Disposable d) { @SuppressWarnings("unchecked") @Override public void onSuccess(@NonNull T value) { - if (value == null) { - onError(new NullPointerException("Null values are not allowed in 2.x")); - return; - } + ObjectHelper.requireNonNull(value, "onSuccess called with null. Null values are generally not allowed in 2.x operators and sources."); if (once.compareAndSet(false, true)) { this.value = value; for (SingleDisposable<T> md : observers.getAndSet(TERMINATED)) { - md.actual.onSuccess(value); + md.downstream.onSuccess(value); } } } @@ -89,13 +146,11 @@ public void onSuccess(@NonNull T value) { @SuppressWarnings("unchecked") @Override public void onError(@NonNull Throwable e) { - if (e == null) { - e = new NullPointerException("Null errors are not allowed in 2.x"); - } + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (once.compareAndSet(false, true)) { this.error = e; for (SingleDisposable<T> md : observers.getAndSet(TERMINATED)) { - md.actual.onError(e); + md.downstream.onError(e); } } else { RxJavaPlugins.onError(e); @@ -234,10 +289,10 @@ static final class SingleDisposable<T> extends AtomicReference<SingleSubject<T>> implements Disposable { private static final long serialVersionUID = -7650903191002190468L; - final SingleObserver<? super T> actual; + final SingleObserver<? super T> downstream; SingleDisposable(SingleObserver<? super T> actual, SingleSubject<T> parent) { - this.actual = actual; + this.downstream = actual; lazySet(parent); } diff --git a/src/main/java/io/reactivex/subjects/Subject.java b/src/main/java/io/reactivex/subjects/Subject.java index 79bcb3934b..4a57a4a77d 100644 --- a/src/main/java/io/reactivex/subjects/Subject.java +++ b/src/main/java/io/reactivex/subjects/Subject.java @@ -17,9 +17,11 @@ import io.reactivex.annotations.*; /** - * Represents an Observer and an Observable at the same time, allowing - * multicasting events from a single source to multiple child Subscribers. - * <p>All methods except the onSubscribe, onNext, onError and onComplete are thread-safe. + * Represents an {@link Observer} and an {@link Observable} at the same time, allowing + * multicasting events from a single source to multiple child {@code Observer}s. + * <p> + * All methods except the {@link #onSubscribe(io.reactivex.disposables.Disposable)}, {@link #onNext(Object)}, + * {@link #onError(Throwable)} and {@link #onComplete()} are thread-safe. * Use {@link #toSerialized()} to make these methods thread-safe as well. * * @param <T> the item value type @@ -37,7 +39,7 @@ public abstract class Subject<T> extends Observable<T> implements Observer<T> { * <p>The method is thread-safe. * @return true if the subject has reached a terminal state through an error event * @see #getThrowable() - * &see {@link #hasComplete()} + * @see #hasComplete() */ public abstract boolean hasThrowable(); diff --git a/src/main/java/io/reactivex/subjects/UnicastSubject.java b/src/main/java/io/reactivex/subjects/UnicastSubject.java index 69543bd752..20aadbd460 100644 --- a/src/main/java/io/reactivex/subjects/UnicastSubject.java +++ b/src/main/java/io/reactivex/subjects/UnicastSubject.java @@ -13,12 +13,13 @@ package io.reactivex.subjects; -import io.reactivex.annotations.Experimental; import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; import io.reactivex.plugins.RxJavaPlugins; + import java.util.concurrent.atomic.*; -import io.reactivex.Observer; +import io.reactivex.*; import io.reactivex.annotations.CheckReturnValue; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.EmptyDisposable; @@ -28,19 +29,115 @@ import io.reactivex.internal.queue.SpscLinkedArrayQueue; /** - * Subject that allows only a single Subscriber to subscribe to it during its lifetime. + * A Subject that queues up events until a single {@link Observer} subscribes to it, replays + * those events to it until the {@code Observer} catches up and then switches to relaying events live to + * this single {@code Observer} until this {@code UnicastSubject} terminates or the {@code Observer} unsubscribes. + * <p> + * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/UnicastSubject.png" alt=""> + * <p> + * Note that {@code UnicastSubject} holds an unbounded internal buffer. + * <p> + * This subject does not have a public constructor by design; a new empty instance of this + * {@code UnicastSubject} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + * <ul> + * <li>{@link #create()} - creates an empty, unbounded {@code UnicastSubject} that + * caches all items and the terminal event it receives.</li> + * <li>{@link #create(int)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many <b>total</b> items one expects to retain.</li> + * <li>{@link #create(boolean)} - creates an empty, unbounded {@code UnicastSubject} that + * optionally delays an error it receives and replays it after the regular items have been emitted.</li> + * <li>{@link #create(int, Runnable)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many <b>total</b> items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastSubject} gets terminated or the single {@code Observer} unsubscribes.</li> + * <li>{@link #create(int, Runnable, boolean)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many <b>total</b> items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastSubject} gets terminated or the single {@code Observer} unsubscribes + * and optionally delays an error it receives and replays it after the regular items have been emitted.</li> + * </ul> + * <p> + * If more than one {@code Observer} attempts to subscribe to this {@code UnicastSubject}, they + * will receive an {@code IllegalStateException} indicating the single-use-only nature of this {@code UnicastSubject}, + * even if the {@code UnicastSubject} already terminated with an error. + * <p> + * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.13">Rule 2.13</a>) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + * <p> + * Since a {@code UnicastSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + * <p> + * When this {@code UnicastSubject} is terminated via {@link #onError(Throwable)} the current or late single {@code Observer} + * may receive the {@code Throwable} before any available items could be emitted. To make sure an onError event is delivered + * to the {@code Observer} after the normal items, create a {@code UnicastSubject} with the {@link #create(boolean)} or + * {@link #create(int, Runnable, boolean)} factory methods. + * <p> + * Even though {@code UnicastSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (<a href="https://github.com/reactive-streams/reactive-streams-jvm#2.12">Rule 2.12</a>) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code UnicastSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + * <p> + * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * <p> + * This {@code UnicastSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()}. + * <dl> + * <dt><b>Scheduler:</b></dt> + * <dd>{@code UnicastSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the single {@code Observer} gets notified on the thread the respective {@code onXXX} methods were invoked.</dd> + * <dt><b>Error handling:</b></dt> + * <dd>When the {@link #onError(Throwable)} is called, the {@code UnicastSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the current single {@code Observer}. During this emission, + * if the single {@code Observer}s disposes its respective {@code Disposable}, the + * {@code Throwable} is delivered to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * If there were no {@code Observer}s subscribed to this {@code UnicastSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + * </dd> + * </dl> + * <p> + * Example usage: + * <pre><code> + * UnicastSubject<Integer> subject = UnicastSubject.create(); * - * <p>This subject buffers notifications and replays them to the Subscriber as requested. + * TestObserver<Integer> to1 = subject.test(); * - * <p>This subject holds an unbounded internal buffer. + * // fresh UnicastSubjects are empty + * to1.assertEmpty(); * - * <p>If more than one Subscriber attempts to subscribe to this Subject, they - * will receive an IllegalStateException if this Subject hasn't terminated yet, - * or the Subscribers receive the terminal event (error or completion) if this - * Subject has terminated. - * <p> - * <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/UnicastSubject.png" alt=""> + * TestObserver<Integer> to2 = subject.test(); + * + * // A UnicastSubject only allows one Observer during its lifetime + * to2.assertFailure(IllegalStateException.class); * + * subject.onNext(1); + * to1.assertValue(1); + * + * subject.onNext(2); + * to1.assertValues(1, 2); + * + * subject.onComplete(); + * to1.assertResult(1, 2); + * + * // ---------------------------------------------------- + * + * UnicastSubject<Integer> subject2 = UnicastSubject.create(); + * + * // a UnicastSubject caches events until its single Observer subscribes + * subject2.onNext(1); + * subject2.onNext(2); + * subject2.onComplete(); + * + * TestObserver<Integer> to3 = subject2.test(); + * + * // the cached events are emitted in order + * to3.assertResult(1, 2); + * </code></pre> * @param <T> the value type received and emitted by this Subject subclass * @since 2.0 */ @@ -49,7 +146,7 @@ public final class UnicastSubject<T> extends Subject<T> { final SpscLinkedArrayQueue<T> queue; /** The single Observer. */ - final AtomicReference<Observer<? super T>> actual; + final AtomicReference<Observer<? super T>> downstream; /** The optional callback when the Subject gets cancelled or terminates. */ final AtomicReference<Runnable> onTerminate; @@ -82,6 +179,7 @@ public final class UnicastSubject<T> extends Subject<T> { * @return an UnicastSubject instance */ @CheckReturnValue + @NonNull public static <T> UnicastSubject<T> create() { return new UnicastSubject<T>(bufferSize(), true); } @@ -93,6 +191,7 @@ public static <T> UnicastSubject<T> create() { * @return an UnicastSubject instance */ @CheckReturnValue + @NonNull public static <T> UnicastSubject<T> create(int capacityHint) { return new UnicastSubject<T>(capacityHint, true); } @@ -110,6 +209,7 @@ public static <T> UnicastSubject<T> create(int capacityHint) { * @return an UnicastSubject instance */ @CheckReturnValue + @NonNull public static <T> UnicastSubject<T> create(int capacityHint, Runnable onTerminate) { return new UnicastSubject<T>(capacityHint, onTerminate, true); } @@ -120,16 +220,16 @@ public static <T> UnicastSubject<T> create(int capacityHint, Runnable onTerminat * * <p>The callback, if not null, is called exactly once and * non-overlapped with any active replay. - * + * <p>History: 2.0.8 - experimental * @param <T> the value type * @param capacityHint the hint to size the internal unbounded buffer * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed * @param delayError deliver pending onNext events before onError * @return an UnicastSubject instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental + @NonNull public static <T> UnicastSubject<T> create(int capacityHint, Runnable onTerminate, boolean delayError) { return new UnicastSubject<T>(capacityHint, onTerminate, delayError); } @@ -139,30 +239,30 @@ public static <T> UnicastSubject<T> create(int capacityHint, Runnable onTerminat * * <p>The callback, if not null, is called exactly once and * non-overlapped with any active replay. - * + * <p>History: 2.0.8 - experimental * @param <T> the value type * @param delayError deliver pending onNext events before onError * @return an UnicastSubject instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental + @NonNull public static <T> UnicastSubject<T> create(boolean delayError) { return new UnicastSubject<T>(bufferSize(), delayError); } - /** * Creates an UnicastSubject with the given capacity hint and delay error flag. + * <p>History: 2.0.8 - experimental * @param capacityHint the capacity hint for the internal, unbounded queue * @param delayError deliver pending onNext events before onError - * @since 2.0.8 - experimental + * @since 2.2 */ UnicastSubject(int capacityHint, boolean delayError) { this.queue = new SpscLinkedArrayQueue<T>(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); this.onTerminate = new AtomicReference<Runnable>(); this.delayError = delayError; - this.actual = new AtomicReference<Observer<? super T>>(); + this.downstream = new AtomicReference<Observer<? super T>>(); this.once = new AtomicBoolean(); this.wip = new UnicastQueueDisposable(); } @@ -182,16 +282,17 @@ public static <T> UnicastSubject<T> create(boolean delayError) { /** * Creates an UnicastSubject with the given capacity hint, delay error flag and callback * for when the Subject is terminated normally or its single Subscriber cancels. + * <p>History: 2.0.8 - experimental * @param capacityHint the capacity hint for the internal, unbounded queue * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed * @param delayError deliver pending onNext events before onError - * @since 2.0.8 - experimental + * @since 2.2 */ UnicastSubject(int capacityHint, Runnable onTerminate, boolean delayError) { this.queue = new SpscLinkedArrayQueue<T>(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); this.onTerminate = new AtomicReference<Runnable>(ObjectHelper.requireNonNull(onTerminate, "onTerminate")); this.delayError = delayError; - this.actual = new AtomicReference<Observer<? super T>>(); + this.downstream = new AtomicReference<Observer<? super T>>(); this.once = new AtomicBoolean(); this.wip = new UnicastQueueDisposable(); } @@ -200,9 +301,9 @@ public static <T> UnicastSubject<T> create(boolean delayError) { protected void subscribeActual(Observer<? super T> observer) { if (!once.get() && once.compareAndSet(false, true)) { observer.onSubscribe(wip); - actual.lazySet(observer); // full barrier in drain + downstream.lazySet(observer); // full barrier in drain if (disposed) { - actual.lazySet(null); + downstream.lazySet(null); return; } drain(); @@ -219,34 +320,29 @@ void doTerminate() { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { if (done || disposed) { - s.dispose(); + d.dispose(); } } @Override public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); if (done || disposed) { return; } - if (t == null) { - onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); - return; - } queue.offer(t); drain(); } @Override public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); if (done || disposed) { RxJavaPlugins.onError(t); return; } - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } error = t; done = true; @@ -276,7 +372,7 @@ void drainNormal(Observer<? super T> a) { for (;;) { if (disposed) { - actual.lazySet(null); + downstream.lazySet(null); q.clear(); return; } @@ -323,8 +419,7 @@ void drainFused(Observer<? super T> a) { for (;;) { if (disposed) { - actual.lazySet(null); - q.clear(); + downstream.lazySet(null); return; } boolean d = done; @@ -350,7 +445,7 @@ void drainFused(Observer<? super T> a) { } void errorOrComplete(Observer<? super T> a) { - actual.lazySet(null); + downstream.lazySet(null); Throwable ex = error; if (ex != null) { a.onError(ex); @@ -362,7 +457,7 @@ void errorOrComplete(Observer<? super T> a) { boolean failedFast(final SimpleQueue<T> q, Observer<? super T> a) { Throwable ex = error; if (ex != null) { - actual.lazySet(null); + downstream.lazySet(null); q.clear(); a.onError(ex); return true; @@ -376,7 +471,7 @@ void drain() { return; } - Observer<? super T> a = actual.get(); + Observer<? super T> a = downstream.get(); int missed = 1; for (;;) { @@ -395,16 +490,17 @@ void drain() { break; } - a = actual.get(); + a = downstream.get(); } } @Override public boolean hasObservers() { - return actual.get() != null; + return downstream.get() != null; } @Override + @Nullable public Throwable getThrowable() { if (done) { return error; @@ -424,7 +520,6 @@ public boolean hasComplete() { final class UnicastQueueDisposable extends BasicIntQueueDisposable<T> { - private static final long serialVersionUID = 7926949470189395511L; @Override @@ -459,10 +554,12 @@ public void dispose() { doTerminate(); - actual.lazySet(null); + downstream.lazySet(null); if (wip.getAndIncrement() == 0) { - actual.lazySet(null); - queue.clear(); + downstream.lazySet(null); + if (!enableOperatorFusion) { + queue.clear(); + } } } } diff --git a/src/main/java/io/reactivex/subjects/package-info.java b/src/main/java/io/reactivex/subjects/package-info.java index e209f5db24..091c223445 100644 --- a/src/main/java/io/reactivex/subjects/package-info.java +++ b/src/main/java/io/reactivex/subjects/package-info.java @@ -15,7 +15,44 @@ */ /** - * Classes extending the Observable base reactive class and implementing - * the Observer interface at the same time (aka hot Observables). + * Classes representing so-called hot sources, aka subjects, that implement a base reactive class and + * the respective consumer type at once to allow forms of multicasting events to multiple + * consumers as well as consuming another base reactive type of their kind. + * <p> + * Available subject classes with their respective base classes and consumer interfaces: + * <br> + * <table border="1" style="border-collapse: collapse;" summary="The available subject classes with their respective base classes and consumer interfaces."> + * <tr><td><b>Subject type</b></td><td><b>Base class</b></td><td><b>Consumer interface</b></td></tr> + * <tr> + * <td>{@link io.reactivex.subjects.Subject Subject} + * <br>   {@link io.reactivex.subjects.AsyncSubject AsyncSubject} + * <br>   {@link io.reactivex.subjects.BehaviorSubject BehaviorSubject} + * <br>   {@link io.reactivex.subjects.PublishSubject PublishSubject} + * <br>   {@link io.reactivex.subjects.ReplaySubject ReplaySubject} + * <br>   {@link io.reactivex.subjects.UnicastSubject UnicastSubject} + * </td> + * <td>{@link io.reactivex.Observable Observable}</td> + * <td>{@link io.reactivex.Observer Observer}</td> + * </tr> + * <tr> + * <td>{@link io.reactivex.subjects.SingleSubject SingleSubject}</td> + * <td>{@link io.reactivex.Single Single}</td> + * <td>{@link io.reactivex.SingleObserver SingleObserver}</td> + * </tr> + * <tr> + * <td>{@link io.reactivex.subjects.MaybeSubject MaybeSubject}</td> + * <td>{@link io.reactivex.Maybe Maybe}</td> + * <td>{@link io.reactivex.MaybeObserver MaybeObserver}</td> + * </tr> + * <tr> + * <td>{@link io.reactivex.subjects.CompletableSubject CompletableSubject}</td> + * <td>{@link io.reactivex.Completable Completable}</td> + * <td>{@link io.reactivex.CompletableObserver CompletableObserver}</td> + * </tr> + * </table> + * <p> + * The backpressure-aware variants of the {@code Subject} class are called + * {@link org.reactivestreams.Processor}s and reside in the {@code io.reactivex.processors} package. + * @see io.reactivex.processors */ package io.reactivex.subjects; diff --git a/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java b/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java index d73c054af7..3038a955fd 100644 --- a/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java @@ -51,7 +51,7 @@ * * <p>Example<pre><code> * Flowable.range(1, 5) - * .subscribe(new DefaultSubscriber<Integer>() { + * .subscribe(new DefaultSubscriber<Integer>() { * @Override public void onStart() { * System.out.println("Start!"); * request(1); @@ -73,11 +73,13 @@ * </code></pre> */ public abstract class DefaultSubscriber<T> implements FlowableSubscriber<T> { - private Subscription s; + + Subscription upstream; + @Override public final void onSubscribe(Subscription s) { - if (EndConsumerHelper.validate(this.s, s, getClass())) { - this.s = s; + if (EndConsumerHelper.validate(this.upstream, s, getClass())) { + this.upstream = s; onStart(); } } @@ -87,7 +89,7 @@ public final void onSubscribe(Subscription s) { * @param n the request amount, positive */ protected final void request(long n) { - Subscription s = this.s; + Subscription s = this.upstream; if (s != null) { s.request(n); } @@ -97,8 +99,8 @@ protected final void request(long n) { * Cancels the upstream's Subscription. */ protected final void cancel() { - Subscription s = this.s; - this.s = SubscriptionHelper.CANCELLED; + Subscription s = this.upstream; + this.upstream = SubscriptionHelper.CANCELLED; s.cancel(); } /** diff --git a/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java index 3cb1be5d70..076dc9427f 100644 --- a/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java @@ -50,7 +50,7 @@ * <p>Example<pre><code> * Disposable d = * Flowable.range(1, 5) - * .subscribeWith(new DisposableSubscriber<Integer>() { + * .subscribeWith(new DisposableSubscriber<Integer>() { * @Override public void onStart() { * request(1); * } @@ -74,11 +74,11 @@ * @param <T> the received value type. */ public abstract class DisposableSubscriber<T> implements FlowableSubscriber<T>, Disposable { - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + final AtomicReference<Subscription> upstream = new AtomicReference<Subscription>(); @Override public final void onSubscribe(Subscription s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { onStart(); } } @@ -87,7 +87,7 @@ public final void onSubscribe(Subscription s) { * Called once the single upstream Subscription is set via onSubscribe. */ protected void onStart() { - s.get().request(Long.MAX_VALUE); + upstream.get().request(Long.MAX_VALUE); } /** @@ -99,7 +99,7 @@ protected void onStart() { * @param n the request amount, positive */ protected final void request(long n) { - s.get().request(n); + upstream.get().request(n); } /** @@ -113,11 +113,11 @@ protected final void cancel() { @Override public final boolean isDisposed() { - return s.get() == SubscriptionHelper.CANCELLED; + return upstream.get() == SubscriptionHelper.CANCELLED; } @Override public final void dispose() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(upstream); } } diff --git a/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java index a90942486c..220fafd191 100644 --- a/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java @@ -63,10 +63,10 @@ * <p>Example<pre><code> * Disposable d = * Flowable.range(1, 5) - * .subscribeWith(new ResourceSubscriber<Integer>() { + * .subscribeWith(new ResourceSubscriber<Integer>() { * @Override public void onStart() { * add(Schedulers.single() - * .scheduleDirect(() -> System.out.println("Time!"), + * .scheduleDirect(() -> System.out.println("Time!"), * 2, TimeUnit.SECONDS)); * request(1); * } @@ -94,7 +94,7 @@ */ public abstract class ResourceSubscriber<T> implements FlowableSubscriber<T>, Disposable { /** The active subscription. */ - private final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + private final AtomicReference<Subscription> upstream = new AtomicReference<Subscription>(); /** The resource composite, can never be null. */ private final ListCompositeDisposable resources = new ListCompositeDisposable(); @@ -116,7 +116,7 @@ public final void add(Disposable resource) { @Override public final void onSubscribe(Subscription s) { - if (EndConsumerHelper.setOnce(this.s, s, getClass())) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { long r = missedRequested.getAndSet(0L); if (r != 0L) { s.request(r); @@ -144,7 +144,7 @@ protected void onStart() { * @param n the request amount, must be positive */ protected final void request(long n) { - SubscriptionHelper.deferredRequest(s, missedRequested, n); + SubscriptionHelper.deferredRequest(upstream, missedRequested, n); } /** @@ -156,7 +156,7 @@ protected final void request(long n) { */ @Override public final void dispose() { - if (SubscriptionHelper.cancel(s)) { + if (SubscriptionHelper.cancel(upstream)) { resources.dispose(); } } @@ -167,6 +167,6 @@ public final void dispose() { */ @Override public final boolean isDisposed() { - return SubscriptionHelper.isCancelled(s.get()); + return upstream.get() == SubscriptionHelper.CANCELLED; } } diff --git a/src/main/java/io/reactivex/subscribers/SafeSubscriber.java b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java index 7c17a472d2..e903a5519c 100644 --- a/src/main/java/io/reactivex/subscribers/SafeSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java @@ -27,26 +27,26 @@ */ public final class SafeSubscriber<T> implements FlowableSubscriber<T>, Subscription { /** The actual Subscriber. */ - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; /** The subscription. */ - Subscription s; + Subscription upstream; /** Indicates a terminal state. */ boolean done; /** * Constructs a SafeSubscriber by wrapping the given actual Subscriber. - * @param actual the actual Subscriber to wrap, not null (not validated) + * @param downstream the actual Subscriber to wrap, not null (not validated) */ - public SafeSubscriber(Subscriber<? super T> actual) { - this.actual = actual; + public SafeSubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.s, s)) { - this.s = s; + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; try { - actual.onSubscribe(this); + downstream.onSubscribe(this); } catch (Throwable e) { Exceptions.throwIfFatal(e); done = true; @@ -68,7 +68,7 @@ public void onNext(T t) { if (done) { return; } - if (s == null) { + if (upstream == null) { onNextNoSubscription(); return; } @@ -76,7 +76,7 @@ public void onNext(T t) { if (t == null) { Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); try { - s.cancel(); + upstream.cancel(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(ex, e1)); @@ -87,11 +87,11 @@ public void onNext(T t) { } try { - actual.onNext(t); + downstream.onNext(t); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { - s.cancel(); + upstream.cancel(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); onError(new CompositeException(e, e1)); @@ -106,7 +106,7 @@ void onNextNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptySubscription.INSTANCE); + downstream.onSubscribe(EmptySubscription.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -114,7 +114,7 @@ void onNextNoSubscription() { return; } try { - actual.onError(ex); + downstream.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins @@ -130,11 +130,11 @@ public void onError(Throwable t) { } done = true; - if (s == null) { + if (upstream == null) { Throwable npe = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptySubscription.INSTANCE); + downstream.onSubscribe(EmptySubscription.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -142,7 +142,7 @@ public void onError(Throwable t) { return; } try { - actual.onError(new CompositeException(t, npe)); + downstream.onError(new CompositeException(t, npe)); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins @@ -156,7 +156,7 @@ public void onError(Throwable t) { } try { - actual.onError(t); + downstream.onError(t); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); @@ -171,14 +171,13 @@ public void onComplete() { } done = true; - if (s == null) { + if (upstream == null) { onCompleteNoSubscription(); return; } - try { - actual.onComplete(); + downstream.onComplete(); } catch (Throwable e) { Exceptions.throwIfFatal(e); RxJavaPlugins.onError(e); @@ -190,7 +189,7 @@ void onCompleteNoSubscription() { Throwable ex = new NullPointerException("Subscription not set!"); try { - actual.onSubscribe(EmptySubscription.INSTANCE); + downstream.onSubscribe(EmptySubscription.INSTANCE); } catch (Throwable e) { Exceptions.throwIfFatal(e); // can't call onError because the actual's state may be corrupt at this point @@ -198,7 +197,7 @@ void onCompleteNoSubscription() { return; } try { - actual.onError(ex); + downstream.onError(ex); } catch (Throwable e) { Exceptions.throwIfFatal(e); // if onError failed, all that's left is to report the error to plugins @@ -209,11 +208,11 @@ void onCompleteNoSubscription() { @Override public void request(long n) { try { - s.request(n); + upstream.request(n); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { - s.cancel(); + upstream.cancel(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); RxJavaPlugins.onError(new CompositeException(e, e1)); @@ -226,7 +225,7 @@ public void request(long n) { @Override public void cancel() { try { - s.cancel(); + upstream.cancel(); } catch (Throwable e1) { Exceptions.throwIfFatal(e1); RxJavaPlugins.onError(e1); diff --git a/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java b/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java index 9743518eea..4e1b147d8e 100644 --- a/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java @@ -31,12 +31,12 @@ * @param <T> the value type */ public final class SerializedSubscriber<T> implements FlowableSubscriber<T>, Subscription { - final Subscriber<? super T> actual; + final Subscriber<? super T> downstream; final boolean delayError; static final int QUEUE_LINK_SIZE = 4; - Subscription subscription; + Subscription upstream; boolean emitting; AppendOnlyLinkedArrayList<Object> queue; @@ -45,10 +45,10 @@ public final class SerializedSubscriber<T> implements FlowableSubscriber<T>, Sub /** * Construct a SerializedSubscriber by wrapping the given actual Subscriber. - * @param actual the actual Subscriber, not null (not verified) + * @param downstream the actual Subscriber, not null (not verified) */ - public SerializedSubscriber(Subscriber<? super T> actual) { - this(actual, false); + public SerializedSubscriber(Subscriber<? super T> downstream) { + this(downstream, false); } /** @@ -59,15 +59,15 @@ public SerializedSubscriber(Subscriber<? super T> actual) { * @param delayError if true, errors are emitted after regular values have been emitted */ public SerializedSubscriber(Subscriber<? super T> actual, boolean delayError) { - this.actual = actual; + this.downstream = actual; this.delayError = delayError; } @Override public void onSubscribe(Subscription s) { - if (SubscriptionHelper.validate(this.subscription, s)) { - this.subscription = s; - actual.onSubscribe(this); + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); } } @@ -77,7 +77,7 @@ public void onNext(T t) { return; } if (t == null) { - subscription.cancel(); + upstream.cancel(); onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); return; } @@ -97,7 +97,7 @@ public void onNext(T t) { emitting = true; } - actual.onNext(t); + downstream.onNext(t); emitLoop(); } @@ -139,7 +139,7 @@ public void onError(Throwable t) { return; } - actual.onError(t); + downstream.onError(t); // no need to loop because this onError is the last event } @@ -165,7 +165,7 @@ public void onComplete() { emitting = true; } - actual.onComplete(); + downstream.onComplete(); // no need to loop because this onComplete is the last event } @@ -181,7 +181,7 @@ void emitLoop() { queue = null; } - if (q.accept(actual)) { + if (q.accept(downstream)) { return; } } @@ -189,11 +189,11 @@ void emitLoop() { @Override public void request(long n) { - subscription.request(n); + upstream.request(n); } @Override public void cancel() { - subscription.cancel(); + upstream.cancel(); } } diff --git a/src/main/java/io/reactivex/subscribers/TestSubscriber.java b/src/main/java/io/reactivex/subscribers/TestSubscriber.java index 07a63a4753..3b02dbdd09 100644 --- a/src/main/java/io/reactivex/subscribers/TestSubscriber.java +++ b/src/main/java/io/reactivex/subscribers/TestSubscriber.java @@ -41,13 +41,13 @@ public class TestSubscriber<T> extends BaseTestConsumer<T, TestSubscriber<T>> implements FlowableSubscriber<T>, Subscription, Disposable { /** The actual subscriber to forward events to. */ - private final Subscriber<? super T> actual; + private final Subscriber<? super T> downstream; /** Makes sure the incoming Subscriptions get cancelled immediately. */ private volatile boolean cancelled; /** Holds the current subscription if any. */ - private final AtomicReference<Subscription> subscription; + private final AtomicReference<Subscription> upstream; /** Holds the requested amount until a subscription arrives. */ private final AtomicLong missedRequested; @@ -102,10 +102,10 @@ public TestSubscriber(long initialRequest) { /** * Constructs a forwarding TestSubscriber but leaves the requesting to the wrapped subscriber. - * @param actual the actual Subscriber to forward events to + * @param downstream the actual Subscriber to forward events to */ - public TestSubscriber(Subscriber<? super T> actual) { - this(actual, Long.MAX_VALUE); + public TestSubscriber(Subscriber<? super T> downstream) { + this(downstream, Long.MAX_VALUE); } /** @@ -120,8 +120,8 @@ public TestSubscriber(Subscriber<? super T> actual, long initialRequest) { if (initialRequest < 0) { throw new IllegalArgumentException("Negative initial request not allowed"); } - this.actual = actual; - this.subscription = new AtomicReference<Subscription>(); + this.downstream = actual; + this.upstream = new AtomicReference<Subscription>(); this.missedRequested = new AtomicLong(initialRequest); } @@ -134,9 +134,9 @@ public void onSubscribe(Subscription s) { errors.add(new NullPointerException("onSubscribe received a null Subscription")); return; } - if (!subscription.compareAndSet(null, s)) { + if (!upstream.compareAndSet(null, s)) { s.cancel(); - if (subscription.get() != SubscriptionHelper.CANCELLED) { + if (upstream.get() != SubscriptionHelper.CANCELLED) { errors.add(new IllegalStateException("onSubscribe received multiple subscriptions: " + s)); } return; @@ -167,8 +167,7 @@ public void onSubscribe(Subscription s) { } } - - actual.onSubscribe(s); + downstream.onSubscribe(s); long mr = missedRequested.getAndSet(0L); if (mr != 0L) { @@ -189,7 +188,7 @@ protected void onStart() { public void onNext(T t) { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new IllegalStateException("onSubscribe not called in proper order")); } } @@ -214,14 +213,14 @@ public void onNext(T t) { errors.add(new NullPointerException("onNext received a null value")); } - actual.onNext(t); + downstream.onNext(t); } @Override public void onError(Throwable t) { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new NullPointerException("onSubscribe not called in proper order")); } } @@ -233,7 +232,7 @@ public void onError(Throwable t) { errors.add(new IllegalStateException("onError received a null Throwable")); } - actual.onError(t); + downstream.onError(t); } finally { done.countDown(); } @@ -243,7 +242,7 @@ public void onError(Throwable t) { public void onComplete() { if (!checkSubscriptionOnce) { checkSubscriptionOnce = true; - if (subscription.get() == null) { + if (upstream.get() == null) { errors.add(new IllegalStateException("onSubscribe not called in proper order")); } } @@ -251,7 +250,7 @@ public void onComplete() { lastThread = Thread.currentThread(); completions++; - actual.onComplete(); + downstream.onComplete(); } finally { done.countDown(); } @@ -259,14 +258,14 @@ public void onComplete() { @Override public final void request(long n) { - SubscriptionHelper.deferredRequest(subscription, missedRequested, n); + SubscriptionHelper.deferredRequest(upstream, missedRequested, n); } @Override public final void cancel() { if (!cancelled) { cancelled = true; - SubscriptionHelper.cancel(subscription); + SubscriptionHelper.cancel(upstream); } } @@ -295,7 +294,7 @@ public final boolean isDisposed() { * @return true if this TestSubscriber received a subscription */ public final boolean hasSubscription() { - return subscription.get() != null; + return upstream.get() != null; } // assertion methods @@ -306,7 +305,7 @@ public final boolean hasSubscription() { */ @Override public final TestSubscriber<T> assertSubscribed() { - if (subscription.get() == null) { + if (upstream.get() == null) { throw fail("Not subscribed!"); } return this; @@ -318,7 +317,7 @@ public final TestSubscriber<T> assertSubscribed() { */ @Override public final TestSubscriber<T> assertNotSubscribed() { - if (subscription.get() != null) { + if (upstream.get() != null) { throw fail("Subscribed!"); } else if (!errors.isEmpty()) { diff --git a/src/main/java/io/reactivex/subscribers/package-info.java b/src/main/java/io/reactivex/subscribers/package-info.java index 22838c91c3..409efd6e79 100644 --- a/src/main/java/io/reactivex/subscribers/package-info.java +++ b/src/main/java/io/reactivex/subscribers/package-info.java @@ -15,7 +15,9 @@ */ /** - * Default wrappers and implementations for Subscriber-based consumer classes and interfaces; - * utility classes for creating them from callbacks. + * Default wrappers and implementations for Subscriber-based consumer classes and interfaces, + * including disposable and resource-tracking variants and + * the {@link io.reactivex.subscribers.TestSubscriber} that allows unit testing + * {@link io.reactivex.Flowable}-based flows. */ package io.reactivex.subscribers; diff --git a/src/main/resources/META-INF/proguard/rxjava2.pro b/src/main/resources/META-INF/proguard/rxjava2.pro new file mode 100644 index 0000000000..d51378e562 --- /dev/null +++ b/src/main/resources/META-INF/proguard/rxjava2.pro @@ -0,0 +1 @@ +-dontwarn java.util.concurrent.Flow* \ No newline at end of file diff --git a/src/test/java/io/reactivex/ConverterTest.java b/src/test/java/io/reactivex/ConverterTest.java new file mode 100644 index 0000000000..d2f174cd2f --- /dev/null +++ b/src/test/java/io/reactivex/ConverterTest.java @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.exceptions.TestException; +import io.reactivex.parallel.*; + +public final class ConverterTest { + + @Test + public void flowableConverterThrows() { + try { + Flowable.just(1).as(new FlowableConverter<Integer, Integer>() { + @Override + public Integer apply(Flowable<Integer> v) { + throw new TestException("Forced failure"); + } + }); + fail("Should have thrown!"); + } catch (TestException ex) { + assertEquals("Forced failure", ex.getMessage()); + } + } + + @Test + public void observableConverterThrows() { + try { + Observable.just(1).as(new ObservableConverter<Integer, Integer>() { + @Override + public Integer apply(Observable<Integer> v) { + throw new TestException("Forced failure"); + } + }); + fail("Should have thrown!"); + } catch (TestException ex) { + assertEquals("Forced failure", ex.getMessage()); + } + } + + @Test + public void singleConverterThrows() { + try { + Single.just(1).as(new SingleConverter<Integer, Integer>() { + @Override + public Integer apply(Single<Integer> v) { + throw new TestException("Forced failure"); + } + }); + fail("Should have thrown!"); + } catch (TestException ex) { + assertEquals("Forced failure", ex.getMessage()); + } + } + + @Test + public void maybeConverterThrows() { + try { + Maybe.just(1).as(new MaybeConverter<Integer, Integer>() { + @Override + public Integer apply(Maybe<Integer> v) { + throw new TestException("Forced failure"); + } + }); + fail("Should have thrown!"); + } catch (TestException ex) { + assertEquals("Forced failure", ex.getMessage()); + } + } + + @Test + public void completableConverterThrows() { + try { + Completable.complete().as(new CompletableConverter<Completable>() { + @Override + public Completable apply(Completable v) { + throw new TestException("Forced failure"); + } + }); + fail("Should have thrown!"); + } catch (TestException ex) { + assertEquals("Forced failure", ex.getMessage()); + } + } + + // Test demos for signature generics in compose() methods. Just needs to compile. + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void observableGenericsSignatureTest() { + A<String, Integer> a = new A<String, Integer>() { }; + + Observable.just(a).as((ObservableConverter)ConverterTest.testObservableConverterCreator()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void singleGenericsSignatureTest() { + A<String, Integer> a = new A<String, Integer>() { }; + + Single.just(a).as((SingleConverter)ConverterTest.<String>testSingleConverterCreator()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void maybeGenericsSignatureTest() { + A<String, Integer> a = new A<String, Integer>() { }; + + Maybe.just(a).as((MaybeConverter)ConverterTest.<String>testMaybeConverterCreator()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void flowableGenericsSignatureTest() { + A<String, Integer> a = new A<String, Integer>() { }; + + Flowable.just(a).as((FlowableConverter)ConverterTest.<String>testFlowableConverterCreator()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void parallelFlowableGenericsSignatureTest() { + A<String, Integer> a = new A<String, Integer>() { }; + + Flowable.just(a).parallel().as((ParallelFlowableConverter)ConverterTest.<String>testParallelFlowableConverterCreator()); + } + + @Test + public void compositeTest() { + CompositeConverter converter = new CompositeConverter(); + + Flowable.just(1) + .as(converter) + .test() + .assertValue(1); + + Observable.just(1) + .as(converter) + .test() + .assertValue(1); + + Maybe.just(1) + .as(converter) + .test() + .assertValue(1); + + Single.just(1) + .as(converter) + .test() + .assertValue(1); + + Completable.complete() + .as(converter) + .test() + .assertComplete(); + + Flowable.just(1) + .parallel() + .as(converter) + .test() + .assertValue(1); + } + + interface A<T, R> { } + interface B<T> { } + + private static <T> ObservableConverter<A<T, ?>, B<T>> testObservableConverterCreator() { + return new ObservableConverter<A<T, ?>, B<T>>() { + @Override + public B<T> apply(Observable<A<T, ?>> a) { + return new B<T>() { + }; + } + }; + } + + private static <T> SingleConverter<A<T, ?>, B<T>> testSingleConverterCreator() { + return new SingleConverter<A<T, ?>, B<T>>() { + @Override + public B<T> apply(Single<A<T, ?>> a) { + return new B<T>() { + }; + } + }; + } + + private static <T> MaybeConverter<A<T, ?>, B<T>> testMaybeConverterCreator() { + return new MaybeConverter<A<T, ?>, B<T>>() { + @Override + public B<T> apply(Maybe<A<T, ?>> a) { + return new B<T>() { + }; + } + }; + } + + private static <T> FlowableConverter<A<T, ?>, B<T>> testFlowableConverterCreator() { + return new FlowableConverter<A<T, ?>, B<T>>() { + @Override + public B<T> apply(Flowable<A<T, ?>> a) { + return new B<T>() { + }; + } + }; + } + + private static <T> ParallelFlowableConverter<A<T, ?>, B<T>> testParallelFlowableConverterCreator() { + return new ParallelFlowableConverter<A<T, ?>, B<T>>() { + @Override + public B<T> apply(ParallelFlowable<A<T, ?>> a) { + return new B<T>() { + }; + } + }; + } + + static class CompositeConverter + implements ObservableConverter<Integer, Flowable<Integer>>, + ParallelFlowableConverter<Integer, Flowable<Integer>>, + FlowableConverter<Integer, Observable<Integer>>, + MaybeConverter<Integer, Flowable<Integer>>, + SingleConverter<Integer, Flowable<Integer>>, + CompletableConverter<Flowable<Integer>> { + @Override + public Flowable<Integer> apply(ParallelFlowable<Integer> upstream) { + return upstream.sequential(); + } + + @Override + public Flowable<Integer> apply(Completable upstream) { + return upstream.toFlowable(); + } + + @Override + public Observable<Integer> apply(Flowable<Integer> upstream) { + return upstream.toObservable(); + } + + @Override + public Flowable<Integer> apply(Maybe<Integer> upstream) { + return upstream.toFlowable(); + } + + @Override + public Flowable<Integer> apply(Observable<Integer> upstream) { + return upstream.toFlowable(BackpressureStrategy.MISSING); + } + + @Override + public Flowable<Integer> apply(Single<Integer> upstream) { + return upstream.toFlowable(); + } + } +} diff --git a/src/test/java/io/reactivex/NotificationTest.java b/src/test/java/io/reactivex/NotificationTest.java index c3283edd3c..504c0425bc 100644 --- a/src/test/java/io/reactivex/NotificationTest.java +++ b/src/test/java/io/reactivex/NotificationTest.java @@ -41,11 +41,11 @@ public void valueOfOnCompleteIsNull() { @Test public void notEqualsToObject() { Notification<Integer> n1 = Notification.createOnNext(0); - assertFalse(n1.equals(0)); + assertNotEquals(0, n1); Notification<Integer> n2 = Notification.createOnError(new TestException()); - assertFalse(n2.equals(0)); + assertNotEquals(0, n2); Notification<Integer> n3 = Notification.createOnComplete(); - assertFalse(n3.equals(0)); + assertNotEquals(0, n3); } @Test diff --git a/src/test/java/io/reactivex/TestHelper.java b/src/test/java/io/reactivex/TestHelper.java index eabe17035b..7d19b95b35 100644 --- a/src/test/java/io/reactivex/TestHelper.java +++ b/src/test/java/io/reactivex/TestHelper.java @@ -50,6 +50,19 @@ */ public enum TestHelper { ; + + /** + * Number of times to loop a {@link #race(Runnable, Runnable)} invocation + * by default. + */ + public static final int RACE_DEFAULT_LOOPS = 2500; + + /** + * Number of times to loop a {@link #race(Runnable, Runnable)} invocation + * in tests with race conditions requiring more runs to check. + */ + public static final int RACE_LONG_LOOPS = 10000; + /** * Mocks a subscriber and prepares it to request Long.MAX_VALUE. * @param <T> the value type @@ -204,8 +217,8 @@ public static void assertUndeliverable(List<Throwable> list, int index, Class<? } } - public static void assertError(TestObserver<?> ts, int index, Class<? extends Throwable> clazz) { - Throwable ex = ts.errors().get(0); + public static void assertError(TestObserver<?> to, int index, Class<? extends Throwable> clazz) { + Throwable ex = to.errors().get(0); try { if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; @@ -231,8 +244,8 @@ public static void assertError(TestSubscriber<?> ts, int index, Class<? extends } } - public static void assertError(TestObserver<?> ts, int index, Class<? extends Throwable> clazz, String message) { - Throwable ex = ts.errors().get(0); + public static void assertError(TestObserver<?> to, int index, Class<? extends Throwable> clazz, String message) { + Throwable ex = to.errors().get(0); if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; List<Throwable> cel = ce.getExceptions(); @@ -344,6 +357,8 @@ public void onComplete() { * <p>The method blocks until both have run to completion. * @param r1 the first runnable * @param r2 the second runnable + * @see #RACE_DEFAULT_LOOPS + * @see #RACE_LONG_LOOPS */ public static void race(final Runnable r1, final Runnable r2) { race(r1, r2, Schedulers.single()); @@ -355,6 +370,8 @@ public static void race(final Runnable r1, final Runnable r2) { * @param r1 the first runnable * @param r2 the second runnable * @param s the scheduler to use + * @see #RACE_DEFAULT_LOOPS + * @see #RACE_LONG_LOOPS */ public static void race(final Runnable r1, final Runnable r2, Scheduler s) { final AtomicInteger count = new AtomicInteger(2); @@ -465,7 +482,7 @@ public static <E extends Enum<E>> void checkEnum(Class<E> enumClass) { } /** - * Returns an Consumer that asserts the TestSubscriber has exaclty one value + completed + * Returns an Consumer that asserts the TestSubscriber has exactly one value + completed * normally and that single value is not the value specified. * @param <T> the value type * @param value the value not expected @@ -488,7 +505,7 @@ public void accept(TestSubscriber<T> ts) throws Exception { } /** - * Returns an Consumer that asserts the TestObserver has exaclty one value + completed + * Returns an Consumer that asserts the TestObserver has exactly one value + completed * normally and that single value is not the value specified. * @param <T> the value type * @param value the value not expected @@ -497,14 +514,14 @@ public void accept(TestSubscriber<T> ts) throws Exception { public static <T> Consumer<TestObserver<T>> observerSingleNot(final T value) { return new Consumer<TestObserver<T>>() { @Override - public void accept(TestObserver<T> ts) throws Exception { - ts + public void accept(TestObserver<T> to) throws Exception { + to .assertSubscribed() .assertValueCount(1) .assertNoErrors() .assertComplete(); - T v = ts.values().get(0); + T v = to.values().get(0); assertNotEquals(value, v); } }; @@ -539,18 +556,18 @@ public static void doubleOnSubscribe(Subscriber<?> subscriber) { /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. - * @param subscriber the target + * @param observer the target */ - public static void doubleOnSubscribe(Observer<?> subscriber) { + public static void doubleOnSubscribe(Observer<?> observer) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); - subscriber.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - subscriber.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -565,18 +582,18 @@ public static void doubleOnSubscribe(Observer<?> subscriber) { /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. - * @param subscriber the target + * @param observer the target */ - public static void doubleOnSubscribe(SingleObserver<?> subscriber) { + public static void doubleOnSubscribe(SingleObserver<?> observer) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); - subscriber.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - subscriber.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -591,18 +608,18 @@ public static void doubleOnSubscribe(SingleObserver<?> subscriber) { /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. - * @param subscriber the target + * @param observer the target */ - public static void doubleOnSubscribe(CompletableObserver subscriber) { + public static void doubleOnSubscribe(CompletableObserver observer) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); - subscriber.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - subscriber.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -617,18 +634,18 @@ public static void doubleOnSubscribe(CompletableObserver subscriber) { /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. - * @param subscriber the target + * @param observer the target */ - public static void doubleOnSubscribe(MaybeObserver<?> subscriber) { + public static void doubleOnSubscribe(MaybeObserver<?> observer) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); - subscriber.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - subscriber.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -1230,6 +1247,60 @@ protected void subscribeActual(SingleObserver<? super T> observer) { } } + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param <T> the input value type + * @param <R> the output value type + * @param transform the transform to drive an operator + */ + public static <T, R> void checkDoubleOnSubscribeSingleToFlowable(Function<Single<T>, ? extends Publisher<R>> transform) { + List<Throwable> errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Single<T> source = new Single<T>() { + @Override + protected void subscribeActual(SingleObserver<? super T> observer) { + try { + Disposable d1 = Disposables.empty(); + + observer.onSubscribe(d1); + + Disposable d2 = Disposables.empty(); + + observer.onSubscribe(d2); + + b[0] = d1.isDisposed(); + b[1] = d2.isDisposed(); + } finally { + cdl.countDown(); + } + } + }; + + Publisher<R> out = transform.apply(source); + + out.subscribe(NoOpConsumer.INSTANCE); + + try { + assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + assertEquals("First disposed?", false, b[0]); + assertEquals("Second not disposed?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + RxJavaPlugins.reset(); + } + } + /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. @@ -1354,16 +1425,16 @@ public static <T, R> void checkDoubleOnSubscribeFlowable(Function<Flowable<T>, ? @Override protected void subscribeActual(Subscriber<? super T> subscriber) { try { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - subscriber.onSubscribe(d1); + subscriber.onSubscribe(bs1); - BooleanSubscription d2 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); - subscriber.onSubscribe(d2); + subscriber.onSubscribe(bs2); - b[0] = d1.isCancelled(); - b[1] = d2.isCancelled(); + b[0] = bs1.isCancelled(); + b[1] = bs2.isCancelled(); } finally { cdl.countDown(); } @@ -1621,18 +1692,18 @@ public static <T, R> void checkDoubleOnSubscribeFlowableToObservable(Function<Fl Flowable<T> source = new Flowable<T>() { @Override - protected void subscribeActual(Subscriber<? super T> observer) { + protected void subscribeActual(Subscriber<? super T> subscriber) { try { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - observer.onSubscribe(d1); + subscriber.onSubscribe(bs1); - BooleanSubscription d2 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); - observer.onSubscribe(d2); + subscriber.onSubscribe(bs2); - b[0] = d1.isCancelled(); - b[1] = d2.isCancelled(); + b[0] = bs1.isCancelled(); + b[1] = bs2.isCancelled(); } finally { cdl.countDown(); } @@ -1675,18 +1746,18 @@ public static <T, R> void checkDoubleOnSubscribeFlowableToSingle(Function<Flowab Flowable<T> source = new Flowable<T>() { @Override - protected void subscribeActual(Subscriber<? super T> observer) { + protected void subscribeActual(Subscriber<? super T> subscriber) { try { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - observer.onSubscribe(d1); + subscriber.onSubscribe(bs1); - BooleanSubscription d2 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); - observer.onSubscribe(d2); + subscriber.onSubscribe(bs2); - b[0] = d1.isCancelled(); - b[1] = d2.isCancelled(); + b[0] = bs1.isCancelled(); + b[1] = bs2.isCancelled(); } finally { cdl.countDown(); } @@ -1729,18 +1800,18 @@ public static <T, R> void checkDoubleOnSubscribeFlowableToMaybe(Function<Flowabl Flowable<T> source = new Flowable<T>() { @Override - protected void subscribeActual(Subscriber<? super T> observer) { + protected void subscribeActual(Subscriber<? super T> subscriber) { try { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - observer.onSubscribe(d1); + subscriber.onSubscribe(bs1); - BooleanSubscription d2 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); - observer.onSubscribe(d2); + subscriber.onSubscribe(bs2); - b[0] = d1.isCancelled(); - b[1] = d2.isCancelled(); + b[0] = bs1.isCancelled(); + b[1] = bs2.isCancelled(); } finally { cdl.countDown(); } @@ -1782,18 +1853,18 @@ public static <T> void checkDoubleOnSubscribeFlowableToCompletable(Function<Flow Flowable<T> source = new Flowable<T>() { @Override - protected void subscribeActual(Subscriber<? super T> observer) { + protected void subscribeActual(Subscriber<? super T> subscriber) { try { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - observer.onSubscribe(d1); + subscriber.onSubscribe(bs1); - BooleanSubscription d2 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); - observer.onSubscribe(d2); + subscriber.onSubscribe(bs2); - b[0] = d1.isCancelled(); - b[1] = d2.isCancelled(); + b[0] = bs1.isCancelled(); + b[1] = bs2.isCancelled(); } finally { cdl.countDown(); } @@ -1979,6 +2050,110 @@ protected void subscribeActual(CompletableObserver observer) { } } + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeCompletableToFlowable(Function<Completable, ? extends Publisher<?>> transform) { + List<Throwable> errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Completable source = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + try { + Disposable d1 = Disposables.empty(); + + observer.onSubscribe(d1); + + Disposable d2 = Disposables.empty(); + + observer.onSubscribe(d2); + + b[0] = d1.isDisposed(); + b[1] = d2.isDisposed(); + } finally { + cdl.countDown(); + } + } + }; + + Publisher<?> out = transform.apply(source); + + out.subscribe(NoOpConsumer.INSTANCE); + + try { + assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + assertEquals("First disposed?", false, b[0]); + assertEquals("Second not disposed?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + RxJavaPlugins.reset(); + } + } + + /** + * Check if the given transformed reactive type reports multiple onSubscribe calls to + * RxJavaPlugins. + * @param transform the transform to drive an operator + */ + public static void checkDoubleOnSubscribeCompletableToObservable(Function<Completable, ? extends ObservableSource<?>> transform) { + List<Throwable> errors = trackPluginErrors(); + try { + final Boolean[] b = { null, null }; + final CountDownLatch cdl = new CountDownLatch(1); + + Completable source = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + try { + Disposable d1 = Disposables.empty(); + + observer.onSubscribe(d1); + + Disposable d2 = Disposables.empty(); + + observer.onSubscribe(d2); + + b[0] = d1.isDisposed(); + b[1] = d2.isDisposed(); + } finally { + cdl.countDown(); + } + } + }; + + ObservableSource<?> out = transform.apply(source); + + out.subscribe(NoOpConsumer.INSTANCE); + + try { + assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + assertEquals("First disposed?", false, b[0]); + assertEquals("Second not disposed?", true, b[1]); + + assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + RxJavaPlugins.reset(); + } + } + /** * Check if the operator applied to a Maybe source propagates dispose properly. * @param <T> the source value type @@ -2096,16 +2271,16 @@ public static void assertCompositeExceptions(TestSubscriber<?> ts, Object... cla /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. - * @param ts the TestSubscriber instance + * @param to the TestSubscriber instance * @param classes the array of expected Throwables inside the Composite */ - public static void assertCompositeExceptions(TestObserver<?> ts, Class<? extends Throwable>... classes) { - ts + public static void assertCompositeExceptions(TestObserver<?> to, Class<? extends Throwable>... classes) { + to .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); - List<Throwable> list = compositeList(ts.errors().get(0)); + List<Throwable> list = compositeList(to.errors().get(0)); assertEquals(classes.length, list.size()); @@ -2117,18 +2292,18 @@ public static void assertCompositeExceptions(TestObserver<?> ts, Class<? extends /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. - * @param ts the TestSubscriber instance + * @param to the TestSubscriber instance * @param classes the array of subsequent Class and String instances representing the * expected Throwable class and the expected error message */ @SuppressWarnings("unchecked") - public static void assertCompositeExceptions(TestObserver<?> ts, Object... classes) { - ts + public static void assertCompositeExceptions(TestObserver<?> to, Object... classes) { + to .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); - List<Throwable> list = compositeList(ts.errors().get(0)); + List<Throwable> list = compositeList(to.errors().get(0)); assertEquals(classes.length, list.size()); @@ -2182,9 +2357,9 @@ public void onSubscribe(Disposable d) { QueueDisposable<Object> qd = (QueueDisposable<Object>) d; state[0] = true; - int m = qd.requestFusion(QueueDisposable.ANY); + int m = qd.requestFusion(QueueFuseable.ANY); - if (m != QueueDisposable.NONE) { + if (m != QueueFuseable.NONE) { state[1] = true; state[2] = qd.isEmpty(); @@ -2241,28 +2416,28 @@ public static <T> void checkFusedIsEmptyClear(Flowable<T> source) { source.subscribe(new FlowableSubscriber<T>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { try { - if (d instanceof QueueSubscription) { + if (s instanceof QueueSubscription) { @SuppressWarnings("unchecked") - QueueSubscription<Object> qd = (QueueSubscription<Object>) d; + QueueSubscription<Object> qs = (QueueSubscription<Object>) s; state[0] = true; - int m = qd.requestFusion(QueueSubscription.ANY); + int m = qs.requestFusion(QueueFuseable.ANY); - if (m != QueueSubscription.NONE) { + if (m != QueueFuseable.NONE) { state[1] = true; - state[2] = qd.isEmpty(); + state[2] = qs.isEmpty(); - qd.clear(); + qs.clear(); - state[3] = qd.isEmpty(); + state[3] = qs.isEmpty(); } } cdl.countDown(); } finally { - d.cancel(); + s.cancel(); } } @@ -2306,11 +2481,11 @@ public static List<Throwable> errorList(TestObserver<?> to) { /** * Returns an expanded error list of the given test consumer. - * @param to the test consumer instance + * @param ts the test consumer instance * @return the list */ - public static List<Throwable> errorList(TestSubscriber<?> to) { - return compositeList(to.errors().get(0)); + public static List<Throwable> errorList(TestSubscriber<?> ts) { + return compositeList(ts.errors().get(0)); } /** @@ -2382,23 +2557,23 @@ protected void subscribeActual(Observer<? super T> observer) { if (o instanceof Publisher) { Publisher<?> os = (Publisher<?>) o; - TestSubscriber<Object> to = new TestSubscriber<Object>(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(); - os.subscribe(to); + os.subscribe(ts); - to.awaitDone(5, TimeUnit.SECONDS); + ts.awaitDone(5, TimeUnit.SECONDS); - to.assertSubscribed(); + ts.assertSubscribed(); if (expected != null) { - to.assertValues(expected); + ts.assertValues(expected); } if (error) { - to.assertError(TestException.class) + ts.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { - to.assertNoErrors().assertComplete(); + ts.assertNoErrors().assertComplete(); } } @@ -2494,24 +2669,24 @@ public static <T> void checkBadSourceFlowable(Function<Flowable<T>, Object> mapp try { Flowable<T> bad = new Flowable<T>() { @Override - protected void subscribeActual(Subscriber<? super T> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super T> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); if (goodValue != null) { - observer.onNext(goodValue); + subscriber.onNext(goodValue); } if (error) { - observer.onError(new TestException("error")); + subscriber.onError(new TestException("error")); } else { - observer.onComplete(); + subscriber.onComplete(); } if (badValue != null) { - observer.onNext(badValue); + subscriber.onNext(badValue); } - observer.onError(new TestException("second")); - observer.onComplete(); + subscriber.onError(new TestException("second")); + subscriber.onComplete(); } }; @@ -2541,23 +2716,23 @@ protected void subscribeActual(Subscriber<? super T> observer) { if (o instanceof Publisher) { Publisher<?> os = (Publisher<?>) o; - TestSubscriber<Object> to = new TestSubscriber<Object>(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(); - os.subscribe(to); + os.subscribe(ts); - to.awaitDone(5, TimeUnit.SECONDS); + ts.awaitDone(5, TimeUnit.SECONDS); - to.assertSubscribed(); + ts.assertSubscribed(); if (expected != null) { - to.assertValues(expected); + ts.assertValues(expected); } if (error) { - to.assertError(TestException.class) + ts.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { - to.assertNoErrors().assertComplete(); + ts.assertNoErrors().assertComplete(); } } @@ -2652,4 +2827,309 @@ public static <T> void checkInvalidParallelSubscribers(ParallelFlowable<T> sourc tss[i].assertFailure(IllegalArgumentException.class); } } + + public static <T> Observable<T> rejectObservableFusion() { + return new Observable<T>() { + @Override + protected void subscribeActual(Observer<? super T> observer) { + observer.onSubscribe(new QueueDisposable<T>() { + + @Override + public int requestFusion(int mode) { + return 0; + } + + @Override + public boolean offer(T value) { + throw new IllegalStateException(); + } + + @Override + public boolean offer(T v1, T v2) { + throw new IllegalStateException(); + } + + @Override + public T poll() throws Exception { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + }); + } + }; + } + + public static <T> Flowable<T> rejectFlowableFusion() { + return new Flowable<T>() { + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + subscriber.onSubscribe(new QueueSubscription<T>() { + + @Override + public int requestFusion(int mode) { + return 0; + } + + @Override + public boolean offer(T value) { + throw new IllegalStateException(); + } + + @Override + public boolean offer(T v1, T v2) { + throw new IllegalStateException(); + } + + @Override + public T poll() throws Exception { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + } + + @Override + public void cancel() { + } + + @Override + public void request(long n) { + } + }); + } + }; + } + + static final class FlowableStripBoundary<T> extends Flowable<T> implements FlowableTransformer<T, T> { + + final Flowable<T> source; + + FlowableStripBoundary(Flowable<T> source) { + this.source = source; + } + + @Override + public Flowable<T> apply(Flowable<T> upstream) { + return new FlowableStripBoundary<T>(upstream); + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new StripBoundarySubscriber<T>(s)); + } + + static final class StripBoundarySubscriber<T> implements FlowableSubscriber<T>, QueueSubscription<T> { + + final Subscriber<? super T> downstream; + + Subscription upstream; + + QueueSubscription<T> qs; + + StripBoundarySubscriber(Subscriber<? super T> downstream) { + this.downstream = downstream; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription subscription) { + this.upstream = subscription; + if (subscription instanceof QueueSubscription) { + qs = (QueueSubscription<T>)subscription; + } + downstream.onSubscribe(this); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable throwable) { + downstream.onError(throwable); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public int requestFusion(int mode) { + QueueSubscription<T> fs = qs; + if (fs != null) { + return fs.requestFusion(mode & ~BOUNDARY); + } + return NONE; + } + + @Override + public boolean offer(T value) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public T poll() throws Exception { + return qs.poll(); + } + + @Override + public void clear() { + qs.clear(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } + } + + public static <T> FlowableTransformer<T, T> flowableStripBoundary() { + return new FlowableStripBoundary<T>(null); + } + + static final class ObservableStripBoundary<T> extends Observable<T> implements ObservableTransformer<T, T> { + + final Observable<T> source; + + ObservableStripBoundary(Observable<T> source) { + this.source = source; + } + + @Override + public Observable<T> apply(Observable<T> upstream) { + return new ObservableStripBoundary<T>(upstream); + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + source.subscribe(new StripBoundaryObserver<T>(observer)); + } + + static final class StripBoundaryObserver<T> implements Observer<T>, QueueDisposable<T> { + + final Observer<? super T> downstream; + + Disposable upstream; + + QueueDisposable<T> qd; + + StripBoundaryObserver(Observer<? super T> downstream) { + this.downstream = downstream; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + if (d instanceof QueueDisposable) { + qd = (QueueDisposable<T>)d; + } + downstream.onSubscribe(this); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable throwable) { + downstream.onError(throwable); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public int requestFusion(int mode) { + QueueDisposable<T> fs = qd; + if (fs != null) { + return fs.requestFusion(mode & ~BOUNDARY); + } + return NONE; + } + + @Override + public boolean offer(T value) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public T poll() throws Exception { + return qd.poll(); + } + + @Override + public void clear() { + qd.clear(); + } + + @Override + public boolean isEmpty() { + return qd.isEmpty(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } + } + + public static <T> ObservableTransformer<T, T> observableStripBoundary() { + return new ObservableStripBoundary<T>(null); + } } diff --git a/src/test/java/io/reactivex/TimesteppingScheduler.java b/src/test/java/io/reactivex/TimesteppingScheduler.java new file mode 100644 index 0000000000..1bf0df2b8e --- /dev/null +++ b/src/test/java/io/reactivex/TimesteppingScheduler.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Scheduler; +import io.reactivex.disposables.*; + +/** + * Basic scheduler that produces an ever increasing {@link #now(TimeUnit)} value. + * Use this scheduler only as a time source! + */ +public class TimesteppingScheduler extends Scheduler { + + final class TimesteppingWorker extends Worker { + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public Disposable schedule(Runnable run, long delay, TimeUnit unit) { + run.run(); + return Disposables.disposed(); + } + + @Override + public long now(TimeUnit unit) { + return TimesteppingScheduler.this.now(unit); + } + } + + public long time; + + public boolean stepEnabled; + + @Override + public Worker createWorker() { + return new TimesteppingWorker(); + } + + @Override + public long now(TimeUnit unit) { + if (stepEnabled) { + return time++; + } + return time; + } +} \ No newline at end of file diff --git a/src/test/java/io/reactivex/XFlatMapTest.java b/src/test/java/io/reactivex/XFlatMapTest.java index 028814e5ec..8a6ec939c5 100644 --- a/src/test/java/io/reactivex/XFlatMapTest.java +++ b/src/test/java/io/reactivex/XFlatMapTest.java @@ -154,7 +154,7 @@ public Maybe<Integer> apply(Integer v) throws Exception { public void flowableCompletable() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Flowable.just(1) + TestObserver<Void> to = Flowable.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -167,13 +167,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -217,7 +217,7 @@ public Completable apply(Integer v) throws Exception { public void observableFlowable() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Observable.just(1) + TestObserver<Integer> to = Observable.just(1) .subscribeOn(Schedulers.io()) .flatMap(new Function<Integer, Observable<Integer>>() { @Override @@ -230,13 +230,13 @@ public Observable<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -248,7 +248,7 @@ public Observable<Integer> apply(Integer v) throws Exception { public void observerSingle() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Observable.just(1) + TestObserver<Integer> to = Observable.just(1) .subscribeOn(Schedulers.io()) .flatMapSingle(new Function<Integer, Single<Integer>>() { @Override @@ -261,13 +261,13 @@ public Single<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -279,7 +279,7 @@ public Single<Integer> apply(Integer v) throws Exception { public void observerMaybe() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Observable.just(1) + TestObserver<Integer> to = Observable.just(1) .subscribeOn(Schedulers.io()) .flatMapMaybe(new Function<Integer, Maybe<Integer>>() { @Override @@ -292,13 +292,13 @@ public Maybe<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -310,7 +310,7 @@ public Maybe<Integer> apply(Integer v) throws Exception { public void observerCompletable() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Observable.just(1) + TestObserver<Void> to = Observable.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -323,13 +323,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -341,7 +341,7 @@ public Completable apply(Integer v) throws Exception { public void observerCompletable2() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Observable.just(1) + TestObserver<Void> to = Observable.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -355,13 +355,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -373,7 +373,7 @@ public Completable apply(Integer v) throws Exception { public void singleSingle() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Single.just(1) + TestObserver<Integer> to = Single.just(1) .subscribeOn(Schedulers.io()) .flatMap(new Function<Integer, Single<Integer>>() { @Override @@ -386,13 +386,13 @@ public Single<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -404,7 +404,7 @@ public Single<Integer> apply(Integer v) throws Exception { public void singleMaybe() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Single.just(1) + TestObserver<Integer> to = Single.just(1) .subscribeOn(Schedulers.io()) .flatMapMaybe(new Function<Integer, Maybe<Integer>>() { @Override @@ -417,13 +417,13 @@ public Maybe<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -435,7 +435,7 @@ public Maybe<Integer> apply(Integer v) throws Exception { public void singleCompletable() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Single.just(1) + TestObserver<Void> to = Single.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -448,13 +448,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -466,7 +466,7 @@ public Completable apply(Integer v) throws Exception { public void singleCompletable2() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Single.just(1) + TestObserver<Integer> to = Single.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -480,13 +480,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -498,7 +498,7 @@ public Completable apply(Integer v) throws Exception { public void maybeSingle() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .subscribeOn(Schedulers.io()) .flatMapSingle(new Function<Integer, Single<Integer>>() { @Override @@ -511,13 +511,13 @@ public Single<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -529,7 +529,7 @@ public Single<Integer> apply(Integer v) throws Exception { public void maybeMaybe() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .subscribeOn(Schedulers.io()) .flatMap(new Function<Integer, Maybe<Integer>>() { @Override @@ -542,13 +542,13 @@ public Maybe<Integer> apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -560,7 +560,7 @@ public Maybe<Integer> apply(Integer v) throws Exception { public void maybeCompletable() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Maybe.just(1) + TestObserver<Void> to = Maybe.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -573,13 +573,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { @@ -591,7 +591,7 @@ public Completable apply(Integer v) throws Exception { public void maybeCompletable2() throws Exception { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Void> ts = Maybe.just(1) + TestObserver<Void> to = Maybe.just(1) .subscribeOn(Schedulers.io()) .flatMapCompletable(new Function<Integer, Completable>() { @Override @@ -605,13 +605,13 @@ public Completable apply(Integer v) throws Exception { cb.await(); - beforeCancelSleep(ts); + beforeCancelSleep(to); - ts.cancel(); + to.cancel(); Thread.sleep(SLEEP_AFTER_CANCEL); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(errors.toString(), errors.isEmpty()); } finally { diff --git a/src/test/java/io/reactivex/completable/CompletableRetryTest.java b/src/test/java/io/reactivex/completable/CompletableRetryTest.java new file mode 100644 index 0000000000..23619078bd --- /dev/null +++ b/src/test/java/io/reactivex/completable/CompletableRetryTest.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2017-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.completable; + +import io.reactivex.Completable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.functions.Functions; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CompletableRetryTest { + @Test + public void retryTimesPredicateWithMatchingPredicate() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Completable.fromAction(new Action() { + @Override public void run() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + throw new IllegalArgumentException(); + } + }) + .retry(Integer.MAX_VALUE, new Predicate<Throwable>() { + @Override public boolean test(final Throwable throwable) throws Exception { + return !(throwable instanceof IllegalArgumentException); + } + }) + .test() + .assertFailure(IllegalArgumentException.class); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Completable.fromAction(new Action() { + @Override public void run() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + } + }) + .retry(2, Functions.alwaysTrue()) + .test() + .assertResult(); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithNotMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Completable.fromAction(new Action() { + @Override public void run() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + } + }) + .retry(1, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(2, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithZeroRetries() { + final AtomicInteger atomicInteger = new AtomicInteger(2); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Completable.fromAction(new Action() { + @Override public void run() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + } + }) + .retry(0, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(1, numberOfSubscribeCalls.get()); + } +} diff --git a/src/test/java/io/reactivex/completable/CompletableTest.java b/src/test/java/io/reactivex/completable/CompletableTest.java index a764d682f1..40e52c8e24 100644 --- a/src/test/java/io/reactivex/completable/CompletableTest.java +++ b/src/test/java/io/reactivex/completable/CompletableTest.java @@ -26,7 +26,7 @@ import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.disposables.*; @@ -105,9 +105,9 @@ static final class NormalCompletable extends AtomicInteger { public final Completable completable = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { getAndIncrement(); - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); } }); @@ -130,9 +130,9 @@ static final class ErrorCompletable extends AtomicInteger { public final Completable completable = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { getAndIncrement(); - EmptyDisposable.error(new TestException(), s); + EmptyDisposable.error(new TestException(), observer); } }); @@ -378,7 +378,7 @@ public void createNull() { public void createOnSubscribeThrowsNPE() { Completable c = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { throw new NullPointerException(); } + public void subscribe(CompletableObserver observer) { throw new NullPointerException(); } }); c.blockingAwait(); @@ -386,10 +386,11 @@ public void createOnSubscribeThrowsNPE() { @Test(timeout = 5000) public void createOnSubscribeThrowsRuntimeException() { + List<Throwable> errors = TestHelper.trackPluginErrors(); try { Completable c = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { throw new TestException(); } }); @@ -402,6 +403,10 @@ public void subscribe(CompletableObserver s) { ex.printStackTrace(); Assert.fail("Did not wrap the TestException but it returned: " + ex); } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); } } @@ -1798,27 +1803,34 @@ public void doOnDisposeNull() { @Test(timeout = 5000) public void doOnDisposeThrows() { - Completable c = normal.completable.doOnDispose(new Action() { - @Override - public void run() { throw new TestException(); } - }); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Completable c = normal.completable.doOnDispose(new Action() { + @Override + public void run() { throw new TestException(); } + }); - c.subscribe(new CompletableObserver() { - @Override - public void onSubscribe(Disposable d) { - d.dispose(); - } + c.subscribe(new CompletableObserver() { + @Override + public void onSubscribe(Disposable d) { + d.dispose(); + } - @Override - public void onError(Throwable e) { - // ignored - } + @Override + public void onError(Throwable e) { + // ignored + } - @Override - public void onComplete() { - // ignored - } - }); + @Override + public void onComplete() { + // ignored + } + }); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(timeout = 5000) @@ -1886,7 +1898,7 @@ public void doOnSubscribeNormal() { Completable c = normal.completable.doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { calls.getAndIncrement(); } }); @@ -2016,6 +2028,7 @@ public void onSubscribe(Disposable d) { }; } } + @Test(timeout = 5000, expected = TestException.class) public void liftOnCompleteError() { Completable c = normal.completable.lift(new CompletableOperatorSwap()); @@ -2157,8 +2170,11 @@ public Completable apply(Throwable e) { try { c.blockingAwait(); Assert.fail("Did not throw an exception"); - } catch (NullPointerException ex) { - Assert.assertTrue(ex.getCause() instanceof TestException); + } catch (CompositeException ex) { + List<Throwable> errors = ex.getExceptions(); + TestHelper.assertError(errors, 0, TestException.class); + TestHelper.assertError(errors, 1, NullPointerException.class); + assertEquals(2, errors.size()); } } @@ -2381,18 +2397,20 @@ public void retryTimes5Error() { @Test(timeout = 5000) public void retryTimes5Normal() { - final AtomicInteger calls = new AtomicInteger(5); + final AtomicInteger calls = new AtomicInteger(); Completable c = Completable.fromAction(new Action() { @Override public void run() { - if (calls.decrementAndGet() != 0) { + if (calls.incrementAndGet() != 6) { throw new TestException(); } } }).retry(5); c.blockingAwait(); + + assertEquals(6, calls.get()); } @Test(expected = IllegalArgumentException.class) @@ -2452,8 +2470,8 @@ public void run() { }).retryWhen(new Function<Flowable<? extends Throwable>, Publisher<Object>>() { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public Publisher<Object> apply(Flowable<? extends Throwable> o) { - return (Publisher)o; + public Publisher<Object> apply(Flowable<? extends Throwable> f) { + return (Publisher)f; } }); @@ -2588,35 +2606,42 @@ public void accept(Throwable e) { @Test(timeout = 5000) public void subscribeTwoCallbacksOnErrorThrows() { - error.completable.subscribe(new Action() { - @Override - public void run() { } - }, new Consumer<Throwable>() { - @Override - public void accept(Throwable e) { throw new TestException(); } - }); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + error.completable.subscribe(new Action() { + @Override + public void run() { } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) { throw new TestException(); } + }); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(timeout = 5000) public void subscribeObserverNormal() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - normal.completable.toObservable().subscribe(ts); + normal.completable.toObservable().subscribe(to); - ts.assertComplete(); - ts.assertNoValues(); - ts.assertNoErrors(); + to.assertComplete(); + to.assertNoValues(); + to.assertNoErrors(); } @Test(timeout = 5000) public void subscribeObserverError() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - error.completable.toObservable().subscribe(ts); + error.completable.toObservable().subscribe(to); - ts.assertNotComplete(); - ts.assertNoValues(); - ts.assertError(TestException.class); + to.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); } @Test(timeout = 5000) @@ -2635,16 +2660,23 @@ public void run() { @Test(timeout = 5000) public void subscribeActionError() { - final AtomicBoolean run = new AtomicBoolean(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicBoolean run = new AtomicBoolean(); - error.completable.subscribe(new Action() { - @Override - public void run() { - run.set(true); - } - }); + error.completable.subscribe(new Action() { + @Override + public void run() { + run.set(true); + } + }); + + Assert.assertFalse("Completed", run.get()); - Assert.assertFalse("Completed", run.get()); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(expected = NullPointerException.class) @@ -2700,9 +2732,9 @@ public void subscribeOnNormal() { Completable c = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { name.set(Thread.currentThread().getName()); - EmptyDisposable.complete(s); + EmptyDisposable.complete(observer); } }).subscribeOn(Schedulers.computation()); @@ -2717,9 +2749,9 @@ public void subscribeOnError() { Completable c = Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { name.set(Thread.currentThread().getName()); - EmptyDisposable.error(new TestException(), s); + EmptyDisposable.error(new TestException(), observer); } }).subscribeOn(Schedulers.computation()); @@ -2783,17 +2815,42 @@ public void timeoutOtherNull() { @Test(timeout = 5000) public void toNormal() { - Flowable<Object> flow = normal.completable.to(new Function<Completable, Flowable<Object>>() { - @Override - public Flowable<Object> apply(Completable c) { - return c.toFlowable(); - } - }); + normal.completable + .to(new Function<Completable, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Completable c) { + return c.toFlowable(); + } + }) + .test() + .assertComplete() + .assertNoValues(); + } - flow.blockingForEach(new Consumer<Object>() { + @Test(timeout = 5000) + public void asNormal() { + normal.completable + .as(new CompletableConverter<Flowable<Object>>() { + @Override + public Flowable<Object> apply(Completable c) { + return c.toFlowable(); + } + }) + .test() + .assertComplete() + .assertNoValues(); + } + + @Test + public void as() { + Completable.complete().as(new CompletableConverter<Flowable<Integer>>() { @Override - public void accept(Object e) { } - }); + public Flowable<Integer> apply(Completable v) { + return v.toFlowable(); + } + }) + .test() + .assertComplete(); } @Test(expected = NullPointerException.class) @@ -2801,6 +2858,11 @@ public void toNull() { normal.completable.to(null); } + @Test(expected = NullPointerException.class) + public void asNull() { + normal.completable.as(null); + } + @Test(timeout = 5000) public void toFlowableNormal() { normal.completable.toFlowable().blockingForEach(Functions.emptyConsumer()); @@ -2948,12 +3010,12 @@ public void ambArraySingleError() { @Test(timeout = 5000) public void ambArrayOneFires() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = Completable.ambArray(c1, c2); @@ -2966,25 +3028,25 @@ public void run() { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps1.onComplete(); + pp1.onComplete(); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get()); } @Test(timeout = 5000) public void ambArrayOneFiresError() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = Completable.ambArray(c1, c2); @@ -2997,25 +3059,25 @@ public void accept(Throwable v) { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps1.onError(new TestException()); + pp1.onError(new TestException()); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get() instanceof TestException); } @Test(timeout = 5000) public void ambArraySecondFires() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = Completable.ambArray(c1, c2); @@ -3028,25 +3090,25 @@ public void run() { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps2.onComplete(); + pp2.onComplete(); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get()); } @Test(timeout = 5000) public void ambArraySecondFiresError() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = Completable.ambArray(c1, c2); @@ -3059,13 +3121,13 @@ public void accept(Throwable v) { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps2.onError(new TestException()); + pp2.onError(new TestException()); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get() instanceof TestException); } @@ -3169,12 +3231,12 @@ public void ambWithNull() { @Test(timeout = 5000) public void ambWithArrayOneFires() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = c1.ambWith(c2); @@ -3187,25 +3249,25 @@ public void run() { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps1.onComplete(); + pp1.onComplete(); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get()); } @Test(timeout = 5000) public void ambWithArrayOneFiresError() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = c1.ambWith(c2); @@ -3218,25 +3280,25 @@ public void accept(Throwable v) { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps1.onError(new TestException()); + pp1.onError(new TestException()); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get() instanceof TestException); } @Test(timeout = 5000) public void ambWithArraySecondFires() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = c1.ambWith(c2); @@ -3249,25 +3311,25 @@ public void run() { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps2.onComplete(); + pp2.onComplete(); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get()); } @Test(timeout = 5000) public void ambWithArraySecondFiresError() { - PublishProcessor<Object> ps1 = PublishProcessor.create(); - PublishProcessor<Object> ps2 = PublishProcessor.create(); + PublishProcessor<Object> pp1 = PublishProcessor.create(); + PublishProcessor<Object> pp2 = PublishProcessor.create(); - Completable c1 = Completable.fromPublisher(ps1); + Completable c1 = Completable.fromPublisher(pp1); - Completable c2 = Completable.fromPublisher(ps2); + Completable c2 = Completable.fromPublisher(pp2); Completable c = c1.ambWith(c2); @@ -3280,13 +3342,13 @@ public void accept(Throwable v) { } }); - Assert.assertTrue("First subject no subscribers", ps1.hasSubscribers()); - Assert.assertTrue("Second subject no subscribers", ps2.hasSubscribers()); + Assert.assertTrue("First subject no subscribers", pp1.hasSubscribers()); + Assert.assertTrue("Second subject no subscribers", pp2.hasSubscribers()); - ps2.onError(new TestException()); + pp2.onError(new TestException()); - Assert.assertFalse("First subject has subscribers", ps1.hasSubscribers()); - Assert.assertFalse("Second subject has subscribers", ps2.hasSubscribers()); + Assert.assertFalse("First subject has subscribers", pp1.hasSubscribers()); + Assert.assertFalse("Second subject has subscribers", pp2.hasSubscribers()); Assert.assertTrue("Not completed", complete.get() instanceof TestException); } @@ -3365,7 +3427,7 @@ public void startWithFlowableError() { @Test(timeout = 5000) public void startWithObservableNormal() { final AtomicBoolean run = new AtomicBoolean(); - Observable<Object> c = normal.completable + Observable<Object> o = normal.completable .startWith(Observable.fromCallable(new Callable<Object>() { @Override public Object call() throws Exception { @@ -3374,32 +3436,32 @@ public Object call() throws Exception { } })); - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - c.subscribe(ts); + o.subscribe(to); Assert.assertTrue("Did not start with other", run.get()); normal.assertSubscriptions(1); - ts.assertValue(1); - ts.assertComplete(); - ts.assertNoErrors(); + to.assertValue(1); + to.assertComplete(); + to.assertNoErrors(); } @Test(timeout = 5000) public void startWithObservableError() { - Observable<Object> c = normal.completable + Observable<Object> o = normal.completable .startWith(Observable.error(new TestException())); - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - c.subscribe(ts); + o.subscribe(to); normal.assertSubscriptions(0); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test(expected = NullPointerException.class) @@ -3437,6 +3499,12 @@ private static void expectUncaughtTestException(Action action) { Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler(); CapturingUncaughtExceptionHandler handler = new CapturingUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(handler); + RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { + @Override + public void accept(Throwable error) throws Exception { + Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), error); + } + }); try { action.run(); assertEquals("Should have received exactly 1 exception", 1, handler.count); @@ -3452,6 +3520,7 @@ private static void expectUncaughtTestException(Action action) { throw ExceptionHelper.wrapOrThrow(ex); } finally { Thread.setDefaultUncaughtExceptionHandler(originalHandler); + RxJavaPlugins.setErrorHandler(null); } } @@ -3545,14 +3614,21 @@ public Completable apply(Integer t) { @Test public void subscribeReportsUnsubscribedOnError() { - PublishSubject<String> stringSubject = PublishSubject.create(); - Completable completable = stringSubject.ignoreElements(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + PublishSubject<String> stringSubject = PublishSubject.create(); + Completable completable = stringSubject.ignoreElements(); - Disposable completableSubscription = completable.subscribe(); + Disposable completableSubscription = completable.subscribe(); - stringSubject.onError(new TestException()); + stringSubject.onError(new TestException()); - assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); + assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); + + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -3577,37 +3653,44 @@ public void subscribeActionReportsUnsubscribedAfter() { PublishSubject<String> stringSubject = PublishSubject.create(); Completable completable = stringSubject.ignoreElements(); - final AtomicReference<Disposable> subscriptionRef = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> disposableRef = new AtomicReference<Disposable>(); Disposable completableSubscription = completable.subscribe(new Action() { @Override public void run() { - if (subscriptionRef.get().isDisposed()) { - subscriptionRef.set(null); + if (disposableRef.get().isDisposed()) { + disposableRef.set(null); } } }); - subscriptionRef.set(completableSubscription); + disposableRef.set(completableSubscription); stringSubject.onComplete(); assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); - assertNotNull("Unsubscribed before the call to onComplete", subscriptionRef.get()); + assertNotNull("Unsubscribed before the call to onComplete", disposableRef.get()); } @Test public void subscribeActionReportsUnsubscribedOnError() { - PublishSubject<String> stringSubject = PublishSubject.create(); - Completable completable = stringSubject.ignoreElements(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + PublishSubject<String> stringSubject = PublishSubject.create(); + Completable completable = stringSubject.ignoreElements(); - Disposable completableSubscription = completable.subscribe(new Action() { - @Override - public void run() { - } - }); + Disposable completableSubscription = completable.subscribe(new Action() { + @Override + public void run() { + } + }); - stringSubject.onError(new TestException()); + stringSubject.onError(new TestException()); - assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); + assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); + + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -3684,9 +3767,9 @@ public void andThenSingleError() { Completable.error(e) .andThen(new Single<String>() { @Override - public void subscribeActual(SingleObserver<? super String> s) { + public void subscribeActual(SingleObserver<? super String> observer) { hasRun.set(true); - s.onSuccess("foo"); + observer.onSuccess("foo"); } }) .toFlowable().subscribe(ts); @@ -4099,7 +4182,7 @@ public void onError(Throwable e) { } @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @@ -4131,7 +4214,7 @@ public void onError(Throwable e) { } @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @@ -4161,8 +4244,8 @@ public void testHookSubscribeStart() throws Exception { TestSubscriber<String> ts = new TestSubscriber<String>(); Completable completable = Completable.unsafeCreate(new CompletableSource() { - @Override public void subscribe(CompletableObserver s) { - s.onComplete(); + @Override public void subscribe(CompletableObserver observer) { + observer.onComplete(); } }); completable.<String>toFlowable().subscribe(ts); @@ -4242,7 +4325,7 @@ public boolean test(Throwable t) { Assert.assertEquals(2, errors.size()); Assert.assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); - Assert.assertEquals(errors.get(0).toString(), null, errors.get(0).getMessage()); + Assert.assertNull(errors.get(0).toString(), errors.get(0).getMessage()); Assert.assertTrue(errors.get(1).toString(), errors.get(1) instanceof TestException); Assert.assertEquals(errors.get(1).toString(), "Forced inner failure", errors.get(1).getMessage()); } @@ -4252,21 +4335,21 @@ public void subscribeAction2ReportsUnsubscribedAfter() { PublishSubject<String> stringSubject = PublishSubject.create(); Completable completable = stringSubject.ignoreElements(); - final AtomicReference<Disposable> subscriptionRef = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> disposableRef = new AtomicReference<Disposable>(); Disposable completableSubscription = completable.subscribe(new Action() { @Override public void run() { - if (subscriptionRef.get().isDisposed()) { - subscriptionRef.set(null); + if (disposableRef.get().isDisposed()) { + disposableRef.set(null); } } }, Functions.emptyConsumer()); - subscriptionRef.set(completableSubscription); + disposableRef.set(completableSubscription); stringSubject.onComplete(); assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); - assertNotNull("Unsubscribed before the call to onComplete", subscriptionRef.get()); + assertNotNull("Unsubscribed before the call to onComplete", disposableRef.get()); } @Test @@ -4274,22 +4357,22 @@ public void subscribeAction2ReportsUnsubscribedOnErrorAfter() { PublishSubject<String> stringSubject = PublishSubject.create(); Completable completable = stringSubject.ignoreElements(); - final AtomicReference<Disposable> subscriptionRef = new AtomicReference<Disposable>(); + final AtomicReference<Disposable> disposableRef = new AtomicReference<Disposable>(); Disposable completableSubscription = completable.subscribe(Functions.EMPTY_ACTION, new Consumer<Throwable>() { @Override public void accept(Throwable e) { - if (subscriptionRef.get().isDisposed()) { - subscriptionRef.set(null); + if (disposableRef.get().isDisposed()) { + disposableRef.set(null); } } }); - subscriptionRef.set(completableSubscription); + disposableRef.set(completableSubscription); stringSubject.onError(new TestException()); assertTrue("Not unsubscribed?", completableSubscription.isDisposed()); - assertNotNull("Unsubscribed before the call to onError", subscriptionRef.get()); + assertNotNull("Unsubscribed before the call to onError", disposableRef.get()); } @Ignore("onXXX methods are not allowed to throw") @@ -4397,8 +4480,9 @@ public void andThenError() { final Exception e = new Exception(); Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver cs) { - cs.onError(e); + public void subscribe(CompletableObserver co) { + co.onSubscribe(Disposables.empty()); + co.onError(e); } }) .andThen(Flowable.<String>unsafeCreate(new Publisher<String>() { @@ -4557,20 +4641,26 @@ public void accept(final Throwable throwable) throws Exception { @Test public void doOnEventError() { - final AtomicInteger atomicInteger = new AtomicInteger(0); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicInteger atomicInteger = new AtomicInteger(0); - Completable.error(new RuntimeException()).doOnEvent(new Consumer<Throwable>() { - @Override - public void accept(final Throwable throwable) throws Exception { - if (throwable != null) { - atomicInteger.incrementAndGet(); + Completable.error(new RuntimeException()).doOnEvent(new Consumer<Throwable>() { + @Override + public void accept(final Throwable throwable) throws Exception { + if (throwable != null) { + atomicInteger.incrementAndGet(); + } } - } - }).subscribe(); + }).subscribe(); - assertEquals(1, atomicInteger.get()); - } + assertEquals(1, atomicInteger.get()); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } + } @Test(timeout = 5000) public void subscribeTwoCallbacksDispose() { diff --git a/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java b/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java index 55daf27e2a..aec399a3e7 100644 --- a/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java +++ b/src/test/java/io/reactivex/disposables/CompositeDisposableTest.java @@ -25,15 +25,14 @@ import io.reactivex.TestHelper; import io.reactivex.exceptions.CompositeException; import io.reactivex.functions.Action; -import io.reactivex.schedulers.Schedulers; public class CompositeDisposableTest { @Test public void testSuccess() { final AtomicInteger counter = new AtomicInteger(); - CompositeDisposable s = new CompositeDisposable(); - s.add(Disposables.fromRunnable(new Runnable() { + CompositeDisposable cd = new CompositeDisposable(); + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -42,7 +41,7 @@ public void run() { })); - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -50,7 +49,7 @@ public void run() { } })); - s.dispose(); + cd.dispose(); assertEquals(2, counter.get()); } @@ -58,12 +57,12 @@ public void run() { @Test(timeout = 1000) public void shouldUnsubscribeAll() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - final CompositeDisposable s = new CompositeDisposable(); + final CompositeDisposable cd = new CompositeDisposable(); final int count = 10; final CountDownLatch start = new CountDownLatch(1); for (int i = 0; i < count; i++) { - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -79,7 +78,7 @@ public void run() { public void run() { try { start.await(); - s.dispose(); + cd.dispose(); } catch (final InterruptedException e) { fail(e.getMessage()); } @@ -100,8 +99,8 @@ public void run() { @Test public void testException() { final AtomicInteger counter = new AtomicInteger(); - CompositeDisposable s = new CompositeDisposable(); - s.add(Disposables.fromRunnable(new Runnable() { + CompositeDisposable cd = new CompositeDisposable(); + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -110,7 +109,7 @@ public void run() { })); - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -120,7 +119,7 @@ public void run() { })); try { - s.dispose(); + cd.dispose(); fail("Expecting an exception"); } catch (RuntimeException e) { // we expect this @@ -134,8 +133,8 @@ public void run() { @Test public void testCompositeException() { final AtomicInteger counter = new AtomicInteger(); - CompositeDisposable s = new CompositeDisposable(); - s.add(Disposables.fromRunnable(new Runnable() { + CompositeDisposable cd = new CompositeDisposable(); + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -144,7 +143,7 @@ public void run() { })); - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -152,7 +151,7 @@ public void run() { } })); - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -162,7 +161,7 @@ public void run() { })); try { - s.dispose(); + cd.dispose(); fail("Expecting an exception"); } catch (CompositeException e) { // we expect this @@ -175,51 +174,51 @@ public void run() { @Test public void testRemoveUnsubscribes() { - Disposable s1 = Disposables.empty(); - Disposable s2 = Disposables.empty(); + Disposable d1 = Disposables.empty(); + Disposable d2 = Disposables.empty(); - CompositeDisposable s = new CompositeDisposable(); - s.add(s1); - s.add(s2); + CompositeDisposable cd = new CompositeDisposable(); + cd.add(d1); + cd.add(d2); - s.remove(s1); + cd.remove(d1); - assertTrue(s1.isDisposed()); - assertFalse(s2.isDisposed()); + assertTrue(d1.isDisposed()); + assertFalse(d2.isDisposed()); } @Test public void testClear() { - Disposable s1 = Disposables.empty(); - Disposable s2 = Disposables.empty(); + Disposable d1 = Disposables.empty(); + Disposable d2 = Disposables.empty(); - CompositeDisposable s = new CompositeDisposable(); - s.add(s1); - s.add(s2); + CompositeDisposable cd = new CompositeDisposable(); + cd.add(d1); + cd.add(d2); - assertFalse(s1.isDisposed()); - assertFalse(s2.isDisposed()); + assertFalse(d1.isDisposed()); + assertFalse(d2.isDisposed()); - s.clear(); + cd.clear(); - assertTrue(s1.isDisposed()); - assertTrue(s2.isDisposed()); - assertFalse(s.isDisposed()); + assertTrue(d1.isDisposed()); + assertTrue(d2.isDisposed()); + assertFalse(cd.isDisposed()); - Disposable s3 = Disposables.empty(); + Disposable d3 = Disposables.empty(); - s.add(s3); - s.dispose(); + cd.add(d3); + cd.dispose(); - assertTrue(s3.isDisposed()); - assertTrue(s.isDisposed()); + assertTrue(d3.isDisposed()); + assertTrue(cd.isDisposed()); } @Test public void testUnsubscribeIdempotence() { final AtomicInteger counter = new AtomicInteger(); - CompositeDisposable s = new CompositeDisposable(); - s.add(Disposables.fromRunnable(new Runnable() { + CompositeDisposable cd = new CompositeDisposable(); + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -228,9 +227,9 @@ public void run() { })); - s.dispose(); - s.dispose(); - s.dispose(); + cd.dispose(); + cd.dispose(); + cd.dispose(); // we should have only disposed once assertEquals(1, counter.get()); @@ -240,11 +239,11 @@ public void run() { public void testUnsubscribeIdempotenceConcurrently() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - final CompositeDisposable s = new CompositeDisposable(); + final CompositeDisposable cd = new CompositeDisposable(); final int count = 10; final CountDownLatch start = new CountDownLatch(1); - s.add(Disposables.fromRunnable(new Runnable() { + cd.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { @@ -260,7 +259,7 @@ public void run() { public void run() { try { start.await(); - s.dispose(); + cd.dispose(); } catch (final InterruptedException e) { fail(e.getMessage()); } @@ -278,24 +277,25 @@ public void run() { // we should have only disposed once assertEquals(1, counter.get()); } + @Test public void testTryRemoveIfNotIn() { - CompositeDisposable csub = new CompositeDisposable(); + CompositeDisposable cd = new CompositeDisposable(); - CompositeDisposable csub1 = new CompositeDisposable(); - CompositeDisposable csub2 = new CompositeDisposable(); + CompositeDisposable cd1 = new CompositeDisposable(); + CompositeDisposable cd2 = new CompositeDisposable(); - csub.add(csub1); - csub.remove(csub1); - csub.add(csub2); + cd.add(cd1); + cd.remove(cd1); + cd.add(cd2); - csub.remove(csub1); // try removing agian + cd.remove(cd1); // try removing agian } @Test(expected = NullPointerException.class) public void testAddingNullDisposableIllegal() { - CompositeDisposable csub = new CompositeDisposable(); - csub.add(null); + CompositeDisposable cd = new CompositeDisposable(); + cd.add(null); } @Test @@ -443,7 +443,7 @@ public void delete() { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @@ -453,13 +453,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @@ -469,13 +469,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addAllRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @@ -485,13 +485,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void removeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -505,13 +505,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void deleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -525,13 +525,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void clearRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -545,13 +545,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @@ -568,13 +568,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void addAllDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @@ -591,13 +591,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void removeDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -618,13 +618,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void deleteDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -645,13 +645,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void clearDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -672,13 +672,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void sizeDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -699,7 +699,7 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } diff --git a/src/test/java/io/reactivex/disposables/DisposablesTest.java b/src/test/java/io/reactivex/disposables/DisposablesTest.java index 3dd90382db..531838acc1 100644 --- a/src/test/java/io/reactivex/disposables/DisposablesTest.java +++ b/src/test/java/io/reactivex/disposables/DisposablesTest.java @@ -27,7 +27,6 @@ import io.reactivex.functions.Action; import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class DisposablesTest { @@ -123,7 +122,7 @@ public void run() throws Exception { @Test public void disposeRace() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Disposable d = Disposables.empty(); Runnable r = new Runnable() { @@ -133,7 +132,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } diff --git a/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java b/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java index ec969189a1..2737b90712 100644 --- a/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java +++ b/src/test/java/io/reactivex/exceptions/CompositeExceptionTest.java @@ -182,6 +182,7 @@ public void testNullCollection() { composite.getCause(); composite.printStackTrace(); } + @Test public void testNullElement() { CompositeException composite = new CompositeException(Collections.singletonList((Throwable) null)); @@ -349,6 +350,37 @@ public void badException() { assertSame(e, new CompositeException(e).getCause().getCause()); assertSame(e, new CompositeException(new RuntimeException(e)).getCause().getCause().getCause()); } + + @Test + public void rootCauseEval() { + final TestException ex0 = new TestException(); + Throwable throwable = new Throwable() { + + private static final long serialVersionUID = 3597694032723032281L; + + @Override + public synchronized Throwable getCause() { + return ex0; + } + }; + CompositeException ex = new CompositeException(throwable); + assertSame(ex0, ex.getRootCause(ex)); + } + + @Test + public void rootCauseSelf() { + Throwable throwable = new Throwable() { + + private static final long serialVersionUID = -4398003222998914415L; + + @Override + public synchronized Throwable getCause() { + return this; + } + }; + CompositeException tmp = new CompositeException(new TestException()); + assertSame(throwable, tmp.getRootCause(throwable)); + } } final class BadException extends Throwable { diff --git a/src/test/java/io/reactivex/exceptions/ExceptionsTest.java b/src/test/java/io/reactivex/exceptions/ExceptionsTest.java index 88a08e8c70..e1cff53e29 100644 --- a/src/test/java/io/reactivex/exceptions/ExceptionsTest.java +++ b/src/test/java/io/reactivex/exceptions/ExceptionsTest.java @@ -53,11 +53,13 @@ public void accept(Integer t1) { }); - TestHelper.assertError(errors, 0, RuntimeException.class, "hello"); + TestHelper.assertError(errors, 0, RuntimeException.class); + assertTrue(errors.get(0).toString(), errors.get(0).getMessage().contains("hello")); RxJavaPlugins.reset(); } /** + * Outdated test: Observer should not suppress errors from onCompleted. * https://github.com/ReactiveX/RxJava/issues/3885 */ @Ignore("v2 components should not throw") @@ -199,6 +201,7 @@ public void onNext(Integer t) { } /** + * Outdated test: throwing from onError handler. * https://github.com/ReactiveX/RxJava/issues/969 */ @Ignore("v2 components should not throw") @@ -236,6 +239,7 @@ public void onNext(Object o) { } /** + * Outdated test: throwing from onError. * https://github.com/ReactiveX/RxJava/issues/2998 * @throws Exception on arbitrary errors */ @@ -275,6 +279,7 @@ public void onNext(GroupedObservable<Integer, Integer> integerIntegerGroupedObse } /** + * Outdated test: throwing from onError. * https://github.com/ReactiveX/RxJava/issues/2998 * @throws Exception on arbitrary errors */ @@ -318,13 +323,13 @@ public void onNext(Integer integer) { public void testOnErrorExceptionIsThrownFromSubscribe() { Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s1) { + public void subscribe(Observer<? super Integer> observer1) { Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s2) { + public void subscribe(Observer<? super Integer> observer2) { throw new IllegalArgumentException("original exception"); } - }).subscribe(s1); + }).subscribe(observer1); } } ).subscribe(new OnErrorFailedSubscriber()); @@ -335,13 +340,13 @@ public void subscribe(Observer<? super Integer> s2) { public void testOnErrorExceptionIsThrownFromUnsafeSubscribe() { Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s1) { + public void subscribe(Observer<? super Integer> observer1) { Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s2) { + public void subscribe(Observer<? super Integer> observer2) { throw new IllegalArgumentException("original exception"); } - }).subscribe(s1); + }).subscribe(observer1); } } ).subscribe(new OnErrorFailedSubscriber()); @@ -365,13 +370,13 @@ public void accept(Integer integer) { public void testOnErrorExceptionIsThrownFromSingleSubscribe() { Single.unsafeCreate(new SingleSource<Integer>() { @Override - public void subscribe(SingleObserver<? super Integer> s1) { + public void subscribe(SingleObserver<? super Integer> observer1) { Single.unsafeCreate(new SingleSource<Integer>() { @Override - public void subscribe(SingleObserver<? super Integer> s2) { + public void subscribe(SingleObserver<? super Integer> observer2) { throw new IllegalArgumentException("original exception"); } - }).subscribe(s1); + }).subscribe(observer1); } } ).toObservable().subscribe(new OnErrorFailedSubscriber()); @@ -382,10 +387,10 @@ public void subscribe(SingleObserver<? super Integer> s2) { public void testOnErrorExceptionIsThrownFromSingleUnsafeSubscribe() { Single.unsafeCreate(new SingleSource<Integer>() { @Override - public void subscribe(final SingleObserver<? super Integer> s1) { + public void subscribe(final SingleObserver<? super Integer> observer1) { Single.unsafeCreate(new SingleSource<Integer>() { @Override - public void subscribe(SingleObserver<? super Integer> s2) { + public void subscribe(SingleObserver<? super Integer> observer2) { throw new IllegalArgumentException("original exception"); } }).toFlowable().subscribe(new FlowableSubscriber<Integer>() { @@ -401,12 +406,12 @@ public void onComplete() { @Override public void onError(Throwable e) { - s1.onError(e); + observer1.onError(e); } @Override public void onNext(Integer v) { - s1.onSuccess(v); + observer1.onSuccess(v); } }); diff --git a/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java b/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java index 1e6b97fc8c..4097f9a52e 100644 --- a/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java +++ b/src/test/java/io/reactivex/exceptions/OnErrorNotImplementedExceptionTest.java @@ -66,6 +66,12 @@ public void flowableBlockingSubscribe1() { .blockingSubscribe(Functions.emptyConsumer()); } + @Test + public void flowableBoundedBlockingSubscribe1() { + Flowable.error(new TestException()) + .blockingSubscribe(Functions.emptyConsumer(), 128); + } + @Test public void observableSubscribe0() { Observable.error(new TestException()) @@ -102,7 +108,6 @@ public void singleSubscribe1() { .subscribe(Functions.emptyConsumer()); } - @Test public void maybeSubscribe0() { Maybe.error(new TestException()) diff --git a/src/test/java/io/reactivex/exceptions/OnNextValueTest.java b/src/test/java/io/reactivex/exceptions/OnNextValueTest.java index 6e0541436b..c0d9df5981 100644 --- a/src/test/java/io/reactivex/exceptions/OnNextValueTest.java +++ b/src/test/java/io/reactivex/exceptions/OnNextValueTest.java @@ -71,7 +71,7 @@ public void onError(Throwable e) { assertTrue(trace, trace.contains("OnNextValue")); - assertTrue("No Cause on throwable" + e, e.getCause() != null); + assertNotNull("No Cause on throwable" + e, e.getCause()); // assertTrue(e.getCause().getClass().getSimpleName() + " no OnNextValue", // e.getCause() instanceof OnErrorThrowable.OnNextValue); } diff --git a/src/test/java/io/reactivex/exceptions/TestException.java b/src/test/java/io/reactivex/exceptions/TestException.java index 6893fe54b4..eef7a47a85 100644 --- a/src/test/java/io/reactivex/exceptions/TestException.java +++ b/src/test/java/io/reactivex/exceptions/TestException.java @@ -52,6 +52,4 @@ public TestException(String message) { public TestException(Throwable cause) { super(cause); } - - } diff --git a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java index 8e658cd5d7..7898628f70 100644 --- a/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableBackpressureTests.java @@ -36,13 +36,13 @@ public class FlowableBackpressureTests { static final class FirehoseNoBackpressure extends AtomicBoolean implements Subscription { private static final long serialVersionUID = -669931580197884015L; - final Subscriber<? super Integer> s; - private final AtomicInteger counter; + final Subscriber<? super Integer> downstream; + final AtomicInteger counter; volatile boolean cancelled; private FirehoseNoBackpressure(AtomicInteger counter, Subscriber<? super Integer> s) { this.counter = counter; - this.s = s; + this.downstream = s; } @Override @@ -52,7 +52,7 @@ public void request(long n) { } if (compareAndSet(false, true)) { int i = 0; - final Subscriber<? super Integer> a = s; + final Subscriber<? super Integer> a = downstream; final AtomicInteger c = counter; while (!cancelled) { @@ -80,20 +80,20 @@ public void doAfterTest() { @Test public void testObserveOn() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - incrementingIntegers(c).observeOn(Schedulers.computation()).take(NUM).subscribe(ts); + incrementingIntegers(c).observeOn(Schedulers.computation()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testObserveOn => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 4); } @Test public void testObserveOnWithSlowConsumer() { - int NUM = (int) (Flowable.bufferSize() * 0.2); + int num = (int) (Flowable.bufferSize() * 0.2); AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); incrementingIntegers(c).observeOn(Schedulers.computation()).map( @@ -108,28 +108,28 @@ public Integer apply(Integer i) { return i; } } - ).take(NUM).subscribe(ts); + ).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testObserveOnWithSlowConsumer => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 2); } @Test public void testMergeSync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable<Integer> merged = Flowable.merge(incrementingIntegers(c1), incrementingIntegers(c2)); - merged.take(NUM).subscribe(ts); + merged.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); - System.out.println("Expected: " + NUM + " got: " + ts.valueCount()); + System.out.println("Expected: " + num + " got: " + ts.valueCount()); System.out.println("testMergeSync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -139,7 +139,7 @@ public void testMergeSync() { @Test public void testMergeAsync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -147,11 +147,11 @@ public void testMergeAsync() { incrementingIntegers(c1).subscribeOn(Schedulers.computation()), incrementingIntegers(c2).subscribeOn(Schedulers.computation())); - merged.take(NUM).subscribe(ts); + merged.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testMergeAsync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -167,7 +167,7 @@ public void testMergeAsyncThenObserveOnLoop() { System.out.println("testMergeAsyncThenObserveOnLoop >> " + i); } // Verify there is no MissingBackpressureException - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); @@ -178,21 +178,20 @@ public void testMergeAsyncThenObserveOnLoop() { merged .observeOn(Schedulers.io()) - .take(NUM) + .take(num) .subscribe(ts); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); ts.assertComplete(); ts.assertNoErrors(); System.out.println("testMergeAsyncThenObserveOn => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); } } @Test public void testMergeAsyncThenObserveOn() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -200,11 +199,11 @@ public void testMergeAsyncThenObserveOn() { incrementingIntegers(c1).subscribeOn(Schedulers.computation()), incrementingIntegers(c2).subscribeOn(Schedulers.computation())); - merged.observeOn(Schedulers.newThread()).take(NUM).subscribe(ts); + merged.observeOn(Schedulers.newThread()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testMergeAsyncThenObserveOn => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // either one can starve the other, but neither should be capable of doing more than 5 batches (taking 4.1) // TODO is it possible to make this deterministic rather than one possibly starving the other? // benjchristensen => In general I'd say it's not worth trying to make it so, as "fair" algoritms generally take a performance hit @@ -215,7 +214,7 @@ public void testMergeAsyncThenObserveOn() { @Test public void testFlatMapSync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -226,20 +225,20 @@ public Publisher<Integer> apply(Integer i) { return incrementingIntegers(new AtomicInteger()).take(10); } }) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testFlatMapSync => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); - // expect less than 1 buffer since the flatMap is emitting 10 each time, so it is NUM/10 that will be taken. + assertEquals(num, ts.valueCount()); + // expect less than 1 buffer since the flatMap is emitting 10 each time, so it is num/10 that will be taken. assertTrue(c.get() < Flowable.bufferSize()); } @Test @Ignore("The test is non-deterministic and can't be made deterministic") public void testFlatMapAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -254,12 +253,12 @@ public Publisher<Integer> apply(Integer i) { } } ) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testFlatMapAsync => Received: " + ts.valueCount() + " Emitted: " + c.get() + " Size: " + Flowable.bufferSize()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // even though we only need 10, it will request at least Flowable.bufferSize(), and then as it drains keep requesting more // and then it will be non-deterministic when the take() causes the unsubscribe as it is scheduled on 10 different schedulers (threads) // normally this number is ~250 but can get up to ~1200 when Flowable.bufferSize() == 1024 @@ -268,7 +267,7 @@ public Publisher<Integer> apply(Integer i) { @Test public void testZipSync() { - int NUM = (int) (Flowable.bufferSize() * 4.1); + int num = (int) (Flowable.bufferSize() * 4.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -283,20 +282,20 @@ public Integer apply(Integer t1, Integer t2) { } }); - zipped.take(NUM) + zipped.take(num) .subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testZipSync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c1.get() < Flowable.bufferSize() * 7); assertTrue(c2.get() < Flowable.bufferSize() * 7); } @Test public void testZipAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c1 = new AtomicInteger(); AtomicInteger c2 = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -310,11 +309,11 @@ public Integer apply(Integer t1, Integer t2) { } }); - zipped.take(NUM).subscribe(ts); + zipped.take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testZipAsync => Received: " + ts.valueCount() + " Emitted: " + c1.get() + " / " + c2.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); int max = Flowable.bufferSize() * 5; assertTrue("" + c1.get() + " >= " + max, c1.get() < max); assertTrue("" + c2.get() + " >= " + max, c2.get() < max); @@ -324,16 +323,16 @@ public Integer apply(Integer t1, Integer t2) { public void testSubscribeOnScheduling() { // in a loop for repeating the concurrency in this to increase chance of failure for (int i = 0; i < 100; i++) { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); ConcurrentLinkedQueue<Thread> threads = new ConcurrentLinkedQueue<Thread>(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); // observeOn is there to make it async and need backpressure - incrementingIntegers(c, threads).subscribeOn(Schedulers.computation()).observeOn(Schedulers.computation()).take(NUM).subscribe(ts); + incrementingIntegers(c, threads).subscribeOn(Schedulers.computation()).observeOn(Schedulers.computation()).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testSubscribeOnScheduling => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < Flowable.bufferSize() * 4); Thread first = null; for (Thread t : threads) { @@ -354,7 +353,7 @@ public void testSubscribeOnScheduling() { @Test public void testTakeFilterSkipChainAsync() { - int NUM = (int) (Flowable.bufferSize() * 2.1); + int num = (int) (Flowable.bufferSize() * 2.1); AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); incrementingIntegers(c).observeOn(Schedulers.computation()) @@ -364,19 +363,19 @@ public void testTakeFilterSkipChainAsync() { public boolean test(Integer i) { return i > 11000; } - }).take(NUM).subscribe(ts); + }).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); // emit 10000 that are skipped // emit next 1000 that are filtered out - // take NUM - // so emitted is at least 10000+1000+NUM + extra for buffer size/threshold + // take num + // so emitted is at least 10000+1000+num + extra for buffer size/threshold int expected = 10000 + 1000 + Flowable.bufferSize() * 3 + Flowable.bufferSize() / 2; System.out.println("testTakeFilterSkipChain => Received: " + ts.valueCount() + " Emitted: " + c.get() + " Expected: " + expected); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); assertTrue(c.get() < expected); } @@ -525,23 +524,23 @@ public void testOnBackpressureDrop() { if (System.currentTimeMillis() - t > TimeUnit.SECONDS.toMillis(9)) { break; } - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); firehose(c).onBackpressureDrop() .observeOn(Schedulers.computation()) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List<Integer> onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); } } @@ -551,7 +550,7 @@ public void testOnBackpressureDropWithAction() { final AtomicInteger emitCount = new AtomicInteger(); final AtomicInteger dropCount = new AtomicInteger(); final AtomicInteger passCount = new AtomicInteger(); - final int NUM = Flowable.bufferSize() * 3; // > 1 so that take doesn't prevent buffer overflow + final int num = Flowable.bufferSize() * 3; // > 1 so that take doesn't prevent buffer overflow TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); firehose(emitCount) @@ -569,19 +568,19 @@ public void accept(Integer v) { }) .observeOn(Schedulers.computation()) .map(SLOW_PASS_THRU) - .take(NUM).subscribe(ts); + .take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List<Integer> onNextEvents = ts.values(); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println(testName.getMethodName() + " => Received: " + onNextEvents.size() + " Passed: " + passCount.get() + " Dropped: " + dropCount.get() + " Emitted: " + emitCount.get() + " Last value: " + lastEvent); - assertEquals(NUM, onNextEvents.size()); - // in reality, NUM < passCount - assertTrue(NUM <= passCount.get()); + assertEquals(num, onNextEvents.size()); + // in reality, num < passCount + assertTrue(num <= passCount.get()); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); assertTrue(0 < dropCount.get()); assertEquals(emitCount.get(), passCount.get() + dropCount.get()); } @@ -590,22 +589,22 @@ public void accept(Integer v) { @Test(timeout = 10000) public void testOnBackpressureDropSynchronous() { for (int i = 0; i < 100; i++) { - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); firehose(c).onBackpressureDrop() - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List<Integer> onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); } } @@ -613,7 +612,7 @@ public void testOnBackpressureDropSynchronous() { public void testOnBackpressureDropSynchronousWithAction() { for (int i = 0; i < 100; i++) { final AtomicInteger dropCount = new AtomicInteger(); - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); firehose(c).onBackpressureDrop(new Consumer<Integer>() { @@ -622,18 +621,18 @@ public void accept(Integer j) { dropCount.incrementAndGet(); } }) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); List<Integer> onNextEvents = ts.values(); - assertEquals(NUM, onNextEvents.size()); + assertEquals(num, onNextEvents.size()); - Integer lastEvent = onNextEvents.get(NUM - 1); + Integer lastEvent = onNextEvents.get(num - 1); System.out.println("testOnBackpressureDrop => Received: " + onNextEvents.size() + " Dropped: " + dropCount.get() + " Emitted: " + c.get() + " Last value: " + lastEvent); // it drop, so we should get some number far higher than what would have sequentially incremented - assertTrue(NUM - 1 <= lastEvent.intValue()); + assertTrue(num - 1 <= lastEvent.intValue()); // no drop in synchronous mode assertEquals(0, dropCount.get()); assertEquals(c.get(), onNextEvents.size()); @@ -642,7 +641,7 @@ public void accept(Integer j) { @Test(timeout = 2000) public void testOnBackpressureBuffer() { - int NUM = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow + int num = (int) (Flowable.bufferSize() * 1.1); // > 1 so that take doesn't prevent buffer overflow AtomicInteger c = new AtomicInteger(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -654,14 +653,14 @@ public boolean test(Integer t1) { }) .onBackpressureBuffer() .observeOn(Schedulers.computation()) - .map(SLOW_PASS_THRU).take(NUM).subscribe(ts); + .map(SLOW_PASS_THRU).take(num).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); System.out.println("testOnBackpressureBuffer => Received: " + ts.valueCount() + " Emitted: " + c.get()); - assertEquals(NUM, ts.valueCount()); + assertEquals(num, ts.valueCount()); // it buffers, so we should get the right value sequentially - assertEquals(NUM - 1, ts.values().get(NUM - 1).intValue()); + assertEquals(num - 1, ts.values().get(num - 1).intValue()); } /** @@ -694,8 +693,8 @@ public void request(long n) { if (threadsSeen != null) { threadsSeen.offer(Thread.currentThread()); } - long _c = BackpressureHelper.add(requested, n); - if (_c == 0) { + long c = BackpressureHelper.add(requested, n); + if (c == 0) { while (!cancelled) { counter.incrementAndGet(); s.onNext(i++); diff --git a/src/test/java/io/reactivex/flowable/FlowableCollectTest.java b/src/test/java/io/reactivex/flowable/FlowableCollectTest.java index ee88dfac53..f187d5b240 100644 --- a/src/test/java/io/reactivex/flowable/FlowableCollectTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableCollectTest.java @@ -31,7 +31,7 @@ public final class FlowableCollectTest { @Test public void testCollectToListFlowable() { - Flowable<List<Integer>> o = Flowable.just(1, 2, 3) + Flowable<List<Integer>> f = Flowable.just(1, 2, 3) .collect(new Callable<List<Integer>>() { @Override public List<Integer> call() { @@ -44,7 +44,7 @@ public void accept(List<Integer> list, Integer v) { } }).toFlowable(); - List<Integer> list = o.blockingLast(); + List<Integer> list = f.blockingLast(); assertEquals(3, list.size()); assertEquals(1, list.get(0).intValue()); @@ -52,7 +52,7 @@ public void accept(List<Integer> list, Integer v) { assertEquals(3, list.get(2).intValue()); // test multiple subscribe - List<Integer> list2 = o.blockingLast(); + List<Integer> list2 = f.blockingLast(); assertEquals(3, list2.size()); assertEquals(1, list2.get(0).intValue()); @@ -83,7 +83,6 @@ public void accept(StringBuilder sb, Integer v) { assertEquals("1-2-3", value); } - @Test public void testFactoryFailureResultsInErrorEmissionFlowable() { final RuntimeException e = new RuntimeException(); @@ -167,7 +166,6 @@ public void accept(Object o, Integer t) { assertFalse(added.get()); } - @SuppressWarnings("unchecked") @Test public void collectIntoFlowable() { @@ -183,7 +181,6 @@ public void accept(HashSet<Integer> s, Integer v) throws Exception { .assertResult(new HashSet<Integer>(Arrays.asList(1, 2))); } - @Test public void testCollectToList() { Single<List<Integer>> o = Flowable.just(1, 2, 3) @@ -238,7 +235,6 @@ public void accept(StringBuilder sb, Integer v) { assertEquals("1-2-3", value); } - @Test public void testFactoryFailureResultsInErrorEmission() { final RuntimeException e = new RuntimeException(); @@ -319,7 +315,6 @@ public void accept(Object o, Integer t) { assertFalse(added.get()); } - @SuppressWarnings("unchecked") @Test public void collectInto() { diff --git a/src/test/java/io/reactivex/flowable/FlowableConcatTests.java b/src/test/java/io/reactivex/flowable/FlowableConcatTests.java index 4a689fe5df..3270204055 100644 --- a/src/test/java/io/reactivex/flowable/FlowableConcatTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableConcatTests.java @@ -26,10 +26,10 @@ public class FlowableConcatTests { @Test public void testConcatSimple() { - Flowable<String> o1 = Flowable.just("one", "two"); - Flowable<String> o2 = Flowable.just("three", "four"); + Flowable<String> f1 = Flowable.just("one", "two"); + Flowable<String> f2 = Flowable.just("three", "four"); - List<String> values = Flowable.concat(o1, o2).toList().blockingGet(); + List<String> values = Flowable.concat(f1, f2).toList().blockingGet(); assertEquals("one", values.get(0)); assertEquals("two", values.get(1)); @@ -39,11 +39,11 @@ public void testConcatSimple() { @Test public void testConcatWithFlowableOfFlowable() { - Flowable<String> o1 = Flowable.just("one", "two"); - Flowable<String> o2 = Flowable.just("three", "four"); - Flowable<String> o3 = Flowable.just("five", "six"); + Flowable<String> f1 = Flowable.just("one", "two"); + Flowable<String> f2 = Flowable.just("three", "four"); + Flowable<String> f3 = Flowable.just("five", "six"); - Flowable<Flowable<String>> os = Flowable.just(o1, o2, o3); + Flowable<Flowable<String>> os = Flowable.just(f1, f2, f3); List<String> values = Flowable.concat(os).toList().blockingGet(); @@ -57,12 +57,12 @@ public void testConcatWithFlowableOfFlowable() { @Test public void testConcatWithIterableOfFlowable() { - Flowable<String> o1 = Flowable.just("one", "two"); - Flowable<String> o2 = Flowable.just("three", "four"); - Flowable<String> o3 = Flowable.just("five", "six"); + Flowable<String> f1 = Flowable.just("one", "two"); + Flowable<String> f2 = Flowable.just("three", "four"); + Flowable<String> f3 = Flowable.just("five", "six"); @SuppressWarnings("unchecked") - Iterable<Flowable<String>> is = Arrays.asList(o1, o2, o3); + Iterable<Flowable<String>> is = Arrays.asList(f1, f2, f3); List<String> values = Flowable.concat(Flowable.fromIterable(is)).toList().blockingGet(); @@ -81,10 +81,10 @@ public void testConcatCovariance() { Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - Flowable<Media> o1 = Flowable.<Media> just(horrorMovie1, movie); - Flowable<Media> o2 = Flowable.just(media, horrorMovie2); + Flowable<Media> f1 = Flowable.<Media> just(horrorMovie1, movie); + Flowable<Media> f2 = Flowable.just(media, horrorMovie2); - Flowable<Flowable<Media>> os = Flowable.just(o1, o2); + Flowable<Flowable<Media>> os = Flowable.just(f1, f2); List<Media> values = Flowable.concat(os).toList().blockingGet(); @@ -103,10 +103,10 @@ public void testConcatCovariance2() { Media media2 = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - Flowable<Media> o1 = Flowable.just(horrorMovie1, movie, media1); - Flowable<Media> o2 = Flowable.just(media2, horrorMovie2); + Flowable<Media> f1 = Flowable.just(horrorMovie1, movie, media1); + Flowable<Media> f2 = Flowable.just(media2, horrorMovie2); - Flowable<Flowable<Media>> os = Flowable.just(o1, o2); + Flowable<Flowable<Media>> os = Flowable.just(f1, f2); List<Media> values = Flowable.concat(os).toList().blockingGet(); @@ -125,10 +125,10 @@ public void testConcatCovariance3() { Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - Flowable<Movie> o1 = Flowable.just(horrorMovie1, movie); - Flowable<Media> o2 = Flowable.just(media, horrorMovie2); + Flowable<Movie> f1 = Flowable.just(horrorMovie1, movie); + Flowable<Media> f2 = Flowable.just(media, horrorMovie2); - List<Media> values = Flowable.concat(o1, o2).toList().blockingGet(); + List<Media> values = Flowable.concat(f1, f2).toList().blockingGet(); assertEquals(horrorMovie1, values.get(0)); assertEquals(movie, values.get(1)); @@ -144,19 +144,19 @@ public void testConcatCovariance4() { Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - Flowable<Movie> o1 = Flowable.unsafeCreate(new Publisher<Movie>() { + Flowable<Movie> f1 = Flowable.unsafeCreate(new Publisher<Movie>() { @Override - public void subscribe(Subscriber<? super Movie> o) { - o.onNext(horrorMovie1); - o.onNext(movie); + public void subscribe(Subscriber<? super Movie> subscriber) { + subscriber.onNext(horrorMovie1); + subscriber.onNext(movie); // o.onNext(new Media()); // correctly doesn't compile - o.onComplete(); + subscriber.onComplete(); } }); - Flowable<Media> o2 = Flowable.just(media, horrorMovie2); + Flowable<Media> f2 = Flowable.just(media, horrorMovie2); - List<Media> values = Flowable.concat(o1, o2).toList().blockingGet(); + List<Media> values = Flowable.concat(f1, f2).toList().blockingGet(); assertEquals(horrorMovie1, values.get(0)); assertEquals(movie, values.get(1)); diff --git a/src/test/java/io/reactivex/flowable/FlowableConversionTest.java b/src/test/java/io/reactivex/flowable/FlowableConversionTest.java index 946a3b4742..36bcb643f6 100644 --- a/src/test/java/io/reactivex/flowable/FlowableConversionTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableConversionTest.java @@ -115,16 +115,16 @@ public RobotConversionFunc(FlowableOperator<? extends R, ? super T> operator) { public CylonDetectorObservable<R> apply(final Publisher<T> onSubscribe) { return CylonDetectorObservable.create(new Publisher<R>() { @Override - public void subscribe(Subscriber<? super R> o) { + public void subscribe(Subscriber<? super R> subscriber) { try { - Subscriber<? super T> st = operator.apply(o); + Subscriber<? super T> st = operator.apply(subscriber); try { onSubscribe.subscribe(st); } catch (Throwable e) { st.onError(e); } } catch (Throwable e) { - o.onError(e); + subscriber.onError(e); } }}); @@ -147,7 +147,7 @@ public Flowable<T> apply(final Publisher<T> onSubscribe) { @Test public void testConversionBetweenObservableClasses() { - final TestObserver<String> subscriber = new TestObserver<String>(new DefaultObserver<String>() { + final TestObserver<String> to = new TestObserver<String>(new DefaultObserver<String>() { @Override public void onComplete() { @@ -196,17 +196,17 @@ public String apply(String a, String n) { return a + n + "\n"; } }) - .subscribe(subscriber); + .subscribe(to); - subscriber.assertNoErrors(); - subscriber.assertComplete(); + to.assertNoErrors(); + to.assertComplete(); } @Test public void testConvertToConcurrentQueue() { final AtomicReference<Throwable> thrown = new AtomicReference<Throwable>(null); final AtomicBoolean isFinished = new AtomicBoolean(false); - ConcurrentLinkedQueue<? extends Integer> queue = Flowable.range(0,5) + ConcurrentLinkedQueue<? extends Integer> queue = Flowable.range(0, 5) .flatMap(new Function<Integer, Publisher<Integer>>() { @Override public Publisher<Integer> apply(final Integer i) { diff --git a/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java b/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java index 2e8e5f1af6..812b747750 100644 --- a/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableCovarianceTest.java @@ -48,7 +48,7 @@ public void testCovarianceOfFrom() { @Test public void testSortedList() { - Comparator<Media> SORT_FUNCTION = new Comparator<Media>() { + Comparator<Media> sortFunction = new Comparator<Media>() { @Override public int compare(Media t1, Media t2) { return 1; @@ -56,12 +56,12 @@ public int compare(Media t1, Media t2) { }; // this one would work without the covariance generics - Flowable<Media> o = Flowable.just(new Movie(), new TVSeason(), new Album()); - o.toSortedList(SORT_FUNCTION); + Flowable<Media> f = Flowable.just(new Movie(), new TVSeason(), new Album()); + f.toSortedList(sortFunction); // this one would NOT work without the covariance generics - Flowable<Movie> o2 = Flowable.just(new Movie(), new ActionMovie(), new HorrorMovie()); - o2.toSortedList(SORT_FUNCTION); + Flowable<Movie> f2 = Flowable.just(new Movie(), new ActionMovie(), new HorrorMovie()); + f2.toSortedList(sortFunction); } @Test diff --git a/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java b/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java index 91baaf1b9c..21f05aa8d4 100644 --- a/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableErrorHandlingTests.java @@ -28,15 +28,16 @@ public class FlowableErrorHandlingTests { /** - * Test that an error from a user provided Observer.onNext is handled and emitted to the onError + * Test that an error from a user provided Observer.onNext + * is handled and emitted to the onError. * @throws InterruptedException if the test is interrupted */ @Test public void testOnNextError() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> caughtError = new AtomicReference<Throwable>(); - Flowable<Long> o = Flowable.interval(50, TimeUnit.MILLISECONDS); - Subscriber<Long> observer = new DefaultSubscriber<Long>() { + Flowable<Long> f = Flowable.interval(50, TimeUnit.MILLISECONDS); + Subscriber<Long> subscriber = new DefaultSubscriber<Long>() { @Override public void onComplete() { @@ -56,14 +57,15 @@ public void onNext(Long args) { throw new RuntimeException("forced failure"); } }; - o.safeSubscribe(observer); + f.safeSubscribe(subscriber); latch.await(2000, TimeUnit.MILLISECONDS); assertNotNull(caughtError.get()); } /** - * Test that an error from a user provided Observer.onNext is handled and emitted to the onError + * Test that an error from a user provided Observer.onNext + * is handled and emitted to the onError. * even when done across thread boundaries with observeOn * @throws InterruptedException if the test is interrupted */ @@ -71,8 +73,8 @@ public void onNext(Long args) { public void testOnNextErrorAcrossThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> caughtError = new AtomicReference<Throwable>(); - Flowable<Long> o = Flowable.interval(50, TimeUnit.MILLISECONDS); - Subscriber<Long> observer = new DefaultSubscriber<Long>() { + Flowable<Long> f = Flowable.interval(50, TimeUnit.MILLISECONDS); + Subscriber<Long> subscriber = new DefaultSubscriber<Long>() { @Override public void onComplete() { @@ -92,8 +94,8 @@ public void onNext(Long args) { throw new RuntimeException("forced failure"); } }; - o.observeOn(Schedulers.newThread()) - .safeSubscribe(observer); + f.observeOn(Schedulers.newThread()) + .safeSubscribe(subscriber); latch.await(2000, TimeUnit.MILLISECONDS); assertNotNull(caughtError.get()); diff --git a/src/test/java/io/reactivex/flowable/FlowableFuseableTest.java b/src/test/java/io/reactivex/flowable/FlowableFuseableTest.java index 64e886e614..f9594a6220 100644 --- a/src/test/java/io/reactivex/flowable/FlowableFuseableTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableFuseableTest.java @@ -17,8 +17,8 @@ import org.junit.Test; import io.reactivex.Flowable; -import io.reactivex.internal.fuseable.QueueSubscription; -import io.reactivex.subscribers.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.subscribers.SubscriberFusion; public class FlowableFuseableTest { @@ -26,8 +26,8 @@ public class FlowableFuseableTest { public void syncRange() { Flowable.range(1, 10) - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -37,8 +37,8 @@ public void syncRange() { public void syncArray() { Flowable.fromArray(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -48,8 +48,8 @@ public void syncArray() { public void syncIterable() { Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -59,9 +59,9 @@ public void syncIterable() { public void syncRangeHidden() { Flowable.range(1, 10).hide() - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) .assertOf(SubscriberFusion.<Integer>assertNotFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -71,9 +71,9 @@ public void syncRangeHidden() { public void syncArrayHidden() { Flowable.fromArray(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) .hide() - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) .assertOf(SubscriberFusion.<Integer>assertNotFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -83,9 +83,9 @@ public void syncArrayHidden() { public void syncIterableHidden() { Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) .hide() - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) .assertOf(SubscriberFusion.<Integer>assertNotFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); diff --git a/src/test/java/io/reactivex/flowable/FlowableMergeTests.java b/src/test/java/io/reactivex/flowable/FlowableMergeTests.java index 46a0278bec..5facc6665f 100644 --- a/src/test/java/io/reactivex/flowable/FlowableMergeTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableMergeTests.java @@ -38,10 +38,10 @@ public void testCovarianceOfMerge() { @Test public void testMergeCovariance() { - Flowable<Media> o1 = Flowable.<Media> just(new HorrorMovie(), new Movie()); - Flowable<Media> o2 = Flowable.just(new Media(), new HorrorMovie()); + Flowable<Media> f1 = Flowable.<Media> just(new HorrorMovie(), new Movie()); + Flowable<Media> f2 = Flowable.just(new Media(), new HorrorMovie()); - Flowable<Flowable<Media>> os = Flowable.just(o1, o2); + Flowable<Flowable<Media>> os = Flowable.just(f1, f2); List<Media> values = Flowable.merge(os).toList().blockingGet(); @@ -50,10 +50,10 @@ public void testMergeCovariance() { @Test public void testMergeCovariance2() { - Flowable<Media> o1 = Flowable.just(new HorrorMovie(), new Movie(), new Media()); - Flowable<Media> o2 = Flowable.just(new Media(), new HorrorMovie()); + Flowable<Media> f1 = Flowable.just(new HorrorMovie(), new Movie(), new Media()); + Flowable<Media> f2 = Flowable.just(new Media(), new HorrorMovie()); - Flowable<Flowable<Media>> os = Flowable.just(o1, o2); + Flowable<Flowable<Media>> os = Flowable.just(f1, f2); List<Media> values = Flowable.merge(os).toList().blockingGet(); @@ -62,21 +62,21 @@ public void testMergeCovariance2() { @Test public void testMergeCovariance3() { - Flowable<Movie> o1 = Flowable.just(new HorrorMovie(), new Movie()); - Flowable<Media> o2 = Flowable.just(new Media(), new HorrorMovie()); + Flowable<Movie> f1 = Flowable.just(new HorrorMovie(), new Movie()); + Flowable<Media> f2 = Flowable.just(new Media(), new HorrorMovie()); - List<Media> values = Flowable.merge(o1, o2).toList().blockingGet(); + List<Media> values = Flowable.merge(f1, f2).toList().blockingGet(); assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } @Test public void testMergeCovariance4() { - Flowable<Movie> o1 = Flowable.defer(new Callable<Publisher<Movie>>() { + Flowable<Movie> f1 = Flowable.defer(new Callable<Publisher<Movie>>() { @Override public Publisher<Movie> call() { return Flowable.just( @@ -86,13 +86,13 @@ public Publisher<Movie> call() { } }); - Flowable<Media> o2 = Flowable.just(new Media(), new HorrorMovie()); + Flowable<Media> f2 = Flowable.just(new Media(), new HorrorMovie()); - List<Media> values = Flowable.merge(o1, o2).toList().blockingGet(); + List<Media> values = Flowable.merge(f1, f2).toList().blockingGet(); assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } diff --git a/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java b/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java index e068cb7399..37b79676d3 100644 --- a/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java @@ -23,28 +23,28 @@ public class FlowableNotificationTest { public void testOnNextIntegerNotificationDoesNotEqualNullNotification() { final Notification<Integer> integerNotification = Notification.createOnNext(1); final Notification<Integer> nullNotification = Notification.createOnNext(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + Assert.assertNotEquals(integerNotification, nullNotification); } @Test(expected = NullPointerException.class) public void testOnNextNullNotificationDoesNotEqualIntegerNotification() { final Notification<Integer> integerNotification = Notification.createOnNext(1); final Notification<Integer> nullNotification = Notification.createOnNext(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + Assert.assertNotEquals(nullNotification, integerNotification); } @Test public void testOnNextIntegerNotificationsWhenEqual() { final Notification<Integer> integerNotification = Notification.createOnNext(1); final Notification<Integer> integerNotification2 = Notification.createOnNext(1); - Assert.assertTrue(integerNotification.equals(integerNotification2)); + Assert.assertEquals(integerNotification, integerNotification2); } @Test public void testOnNextIntegerNotificationsWhenNotEqual() { final Notification<Integer> integerNotification = Notification.createOnNext(1); final Notification<Integer> integerNotification2 = Notification.createOnNext(2); - Assert.assertFalse(integerNotification.equals(integerNotification2)); + Assert.assertNotEquals(integerNotification, integerNotification2); } @Test @@ -52,7 +52,7 @@ public void testOnNextIntegerNotificationsWhenNotEqual() { public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { final Notification<Integer> integerNotification = Notification.createOnError(new Exception()); final Notification<Integer> nullNotification = Notification.createOnError(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + Assert.assertNotEquals(integerNotification, nullNotification); } @Test @@ -60,7 +60,7 @@ public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { public void testOnErrorNullNotificationDoesNotEqualIntegerNotification() { final Notification<Integer> integerNotification = Notification.createOnError(new Exception()); final Notification<Integer> nullNotification = Notification.createOnError(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + Assert.assertNotEquals(nullNotification, integerNotification); } @Test @@ -68,13 +68,13 @@ public void testOnErrorIntegerNotificationsWhenEqual() { final Exception exception = new Exception(); final Notification<Integer> onErrorNotification = Notification.createOnError(exception); final Notification<Integer> onErrorNotification2 = Notification.createOnError(exception); - Assert.assertTrue(onErrorNotification.equals(onErrorNotification2)); + Assert.assertEquals(onErrorNotification, onErrorNotification2); } @Test public void testOnErrorIntegerNotificationWhenNotEqual() { final Notification<Integer> onErrorNotification = Notification.createOnError(new Exception()); final Notification<Integer> onErrorNotification2 = Notification.createOnError(new Exception()); - Assert.assertFalse(onErrorNotification.equals(onErrorNotification2)); + Assert.assertNotEquals(onErrorNotification, onErrorNotification2); } } diff --git a/src/test/java/io/reactivex/flowable/FlowableNullTests.java b/src/test/java/io/reactivex/flowable/FlowableNullTests.java index 7ff326dfbf..a9acc4f061 100644 --- a/src/test/java/io/reactivex/flowable/FlowableNullTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableNullTests.java @@ -466,7 +466,7 @@ public void intervalPeriodSchedulerNull() { @Test(expected = NullPointerException.class) public void intervalRangeUnitNull() { - Flowable.intervalRange(1,1, 1, 1, null); + Flowable.intervalRange(1, 1, 1, 1, null); } @Test(expected = NullPointerException.class) @@ -999,7 +999,7 @@ public Iterator<Object> iterator() { @Test(expected = NullPointerException.class) public void concatWithNull() { - just1.concatWith(null); + just1.concatWith((Publisher<Integer>)null); } @Test(expected = NullPointerException.class) @@ -1620,7 +1620,7 @@ public Object apply(Integer v) { @Test(expected = NullPointerException.class) public void mergeWithNull() { - just1.mergeWith(null); + just1.mergeWith((Publisher<Integer>)null); } @Test(expected = NullPointerException.class) @@ -1648,14 +1648,22 @@ public void onErrorResumeNextFunctionNull() { just1.onErrorResumeNext((Function<Throwable, Publisher<Integer>>)null); } - @Test(expected = NullPointerException.class) + @Test public void onErrorResumeNextFunctionReturnsNull() { - Flowable.error(new TestException()).onErrorResumeNext(new Function<Throwable, Publisher<Object>>() { - @Override - public Publisher<Object> apply(Throwable e) { - return null; - } - }).blockingSubscribe(); + try { + Flowable.error(new TestException()).onErrorResumeNext(new Function<Throwable, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Throwable e) { + return null; + } + }).blockingSubscribe(); + fail("Should have thrown"); + } catch (CompositeException ex) { + List<Throwable> errors = ex.getExceptions(); + TestHelper.assertError(errors, 0, TestException.class); + TestHelper.assertError(errors, 1, NullPointerException.class); + assertEquals(2, errors.size()); + } } @Test(expected = NullPointerException.class) @@ -1805,7 +1813,7 @@ public void replaySelectorNull() { public void replaySelectorReturnsNull() { just1.replay(new Function<Flowable<Integer>, Publisher<Object>>() { @Override - public Publisher<Object> apply(Flowable<Integer> o) { + public Publisher<Object> apply(Flowable<Integer> f) { return null; } }).blockingSubscribe(); @@ -2328,7 +2336,7 @@ public void timeoutFirstItemNull() { @Test(expected = NullPointerException.class) public void timeoutFirstItemReturnsNull() { - just1.timeout(just1, new Function<Integer, Publisher<Object>>() { + just1.timeout(Flowable.never(), new Function<Integer, Publisher<Object>>() { @Override public Publisher<Object> apply(Integer v) { return null; @@ -2351,6 +2359,11 @@ public void toNull() { just1.to(null); } + @Test(expected = NullPointerException.class) + public void asNull() { + just1.as(null); + } + @Test(expected = NullPointerException.class) public void toListNull() { just1.toList(null); @@ -2711,7 +2724,6 @@ public Object apply(Integer a, Integer b) { }); } - @Test(expected = NullPointerException.class) public void zipWithCombinerNull() { just1.zipWith(just1, null); @@ -2733,72 +2745,72 @@ public Object apply(Integer a, Integer b) { @Test(expected = NullPointerException.class) public void asyncSubjectOnNextNull() { - FlowableProcessor<Integer> subject = AsyncProcessor.create(); - subject.onNext(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = AsyncProcessor.create(); + processor.onNext(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void asyncSubjectOnErrorNull() { - FlowableProcessor<Integer> subject = AsyncProcessor.create(); - subject.onError(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = AsyncProcessor.create(); + processor.onError(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void behaviorSubjectOnNextNull() { - FlowableProcessor<Integer> subject = BehaviorProcessor.create(); - subject.onNext(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = BehaviorProcessor.create(); + processor.onNext(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void behaviorSubjectOnErrorNull() { - FlowableProcessor<Integer> subject = BehaviorProcessor.create(); - subject.onError(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = BehaviorProcessor.create(); + processor.onError(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void publishSubjectOnNextNull() { - FlowableProcessor<Integer> subject = PublishProcessor.create(); - subject.onNext(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = PublishProcessor.create(); + processor.onNext(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void publishSubjectOnErrorNull() { - FlowableProcessor<Integer> subject = PublishProcessor.create(); - subject.onError(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = PublishProcessor.create(); + processor.onError(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void replaycSubjectOnNextNull() { - FlowableProcessor<Integer> subject = ReplayProcessor.create(); - subject.onNext(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = ReplayProcessor.create(); + processor.onNext(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void replaySubjectOnErrorNull() { - FlowableProcessor<Integer> subject = ReplayProcessor.create(); - subject.onError(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = ReplayProcessor.create(); + processor.onError(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void serializedcSubjectOnNextNull() { - FlowableProcessor<Integer> subject = PublishProcessor.<Integer>create().toSerialized(); - subject.onNext(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = PublishProcessor.<Integer>create().toSerialized(); + processor.onNext(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) public void serializedSubjectOnErrorNull() { - FlowableProcessor<Integer> subject = PublishProcessor.<Integer>create().toSerialized(); - subject.onError(null); - subject.blockingSubscribe(); + FlowableProcessor<Integer> processor = PublishProcessor.<Integer>create().toSerialized(); + processor.onError(null); + processor.blockingSubscribe(); } @Test(expected = NullPointerException.class) diff --git a/src/test/java/io/reactivex/flowable/FlowableReduceTests.java b/src/test/java/io/reactivex/flowable/FlowableReduceTests.java index b21821e746..8381a510a4 100644 --- a/src/test/java/io/reactivex/flowable/FlowableReduceTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableReduceTests.java @@ -25,8 +25,8 @@ public class FlowableReduceTests { @Test public void reduceIntsFlowable() { - Flowable<Integer> o = Flowable.just(1, 2, 3); - int value = o.reduce(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> f = Flowable.just(1, 2, 3); + int value = f.reduce(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -77,11 +77,10 @@ public Movie apply(Movie t1, Movie t2) { assertNotNull(reduceResult2); } - @Test public void reduceInts() { - Flowable<Integer> o = Flowable.just(1, 2, 3); - int value = o.reduce(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> f = Flowable.just(1, 2, 3); + int value = f.reduce(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; diff --git a/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java b/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java index e9e0da5159..d0a0e1a88e 100644 --- a/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableSubscriberTest.java @@ -231,9 +231,8 @@ public void onError(Throwable e) { public void onNext(String t) { } - - }; + return as; } }; @@ -468,7 +467,7 @@ public void onNext(Integer t) { public void testNegativeRequestThrowsIllegalArgumentException() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); - Flowable.just(1,2,3,4).subscribe(new DefaultSubscriber<Integer>() { + Flowable.just(1, 2, 3, 4).subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { @@ -499,7 +498,8 @@ public void onNext(Integer t) { @Test public void testOnStartRequestsAreAdditive() { final List<Integer> list = new ArrayList<Integer>(); - Flowable.just(1,2,3,4,5).subscribe(new DefaultSubscriber<Integer>() { + Flowable.just(1, 2, 3, 4, 5) + .subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { request(3); @@ -520,13 +520,13 @@ public void onError(Throwable e) { public void onNext(Integer t) { list.add(t); }}); - assertEquals(Arrays.asList(1,2,3,4,5), list); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } @Test public void testOnStartRequestsAreAdditiveAndOverflowBecomesMaxValue() { final List<Integer> list = new ArrayList<Integer>(); - Flowable.just(1,2,3,4,5).subscribe(new DefaultSubscriber<Integer>() { + Flowable.just(1, 2, 3, 4, 5).subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { request(2); @@ -547,7 +547,7 @@ public void onError(Throwable e) { public void onNext(Integer t) { list.add(t); }}); - assertEquals(Arrays.asList(1,2,3,4,5), list); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } @Test @@ -589,10 +589,10 @@ public boolean test(Integer v) throws Exception { try { s.onSubscribe(new BooleanSubscription()); - BooleanSubscription d = new BooleanSubscription(); - s.onSubscribe(d); + BooleanSubscription bs = new BooleanSubscription(); + s.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); TestHelper.assertError(list, 0, IllegalStateException.class, "Subscription already set!"); } finally { @@ -602,33 +602,40 @@ public boolean test(Integer v) throws Exception { @Test public void suppressAfterCompleteEvents() { - final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - ts.onSubscribe(new BooleanSubscription()); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + ForEachWhileSubscriber<Integer> s = new ForEachWhileSubscriber<Integer>(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + ts.onNext(v); + return true; + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + ts.onError(e); + } + }, new Action() { + @Override + public void run() throws Exception { + ts.onComplete(); + } + }); - ForEachWhileSubscriber<Integer> s = new ForEachWhileSubscriber<Integer>(new Predicate<Integer>() { - @Override - public boolean test(Integer v) throws Exception { - ts.onNext(v); - return true; - } - }, new Consumer<Throwable>() { - @Override - public void accept(Throwable e) throws Exception { - ts.onError(e); - } - }, new Action() { - @Override - public void run() throws Exception { - ts.onComplete(); - } - }); + s.onComplete(); + s.onNext(1); + s.onError(new TestException()); + s.onComplete(); - s.onComplete(); - s.onNext(1); - s.onError(new TestException()); - s.onComplete(); + ts.assertResult(); - ts.assertResult(); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -749,11 +756,11 @@ public void accept(Throwable e) throws Exception { @Test public void methodTestCancelled() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.test(Long.MAX_VALUE, true); + pp.test(Long.MAX_VALUE, true); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test @@ -764,14 +771,13 @@ public void safeSubscriberAlreadySafe() { ts.assertResult(1); } - @Test public void methodTestNoCancel() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.test(Long.MAX_VALUE, false); + pp.test(Long.MAX_VALUE, false); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); } @Test @@ -809,7 +815,7 @@ public Subscriber apply(Flowable a, Subscriber b) throws Exception { Flowable.just(1).test(); fail("Should have thrown"); } catch (NullPointerException ex) { - assertEquals("Plugin returned null Subscriber", ex.getMessage()); + assertEquals("The RxJavaPlugins.onSubscribe hook returned a null FlowableSubscriber. Please check the handler provided to RxJavaPlugins.setOnFlowableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins", ex.getMessage()); } } finally { RxJavaPlugins.reset(); diff --git a/src/test/java/io/reactivex/flowable/FlowableTests.java b/src/test/java/io/reactivex/flowable/FlowableTests.java index 29e0d6974a..c9e226da9e 100644 --- a/src/test/java/io/reactivex/flowable/FlowableTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableTests.java @@ -25,11 +25,14 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.Observable; import io.reactivex.disposables.Disposable; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; @@ -96,24 +99,24 @@ public void fromArityArgs1() { @Test public void testCreate() { - Flowable<String> observable = Flowable.just("one", "two", "three"); + Flowable<String> flowable = Flowable.just("one", "two", "three"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testCountAFewItemsFlowable() { - Flowable<String> observable = Flowable.just("a", "b", "c", "d"); + Flowable<String> flowable = Flowable.just("a", "b", "c", "d"); - observable.count().toFlowable().subscribe(w); + flowable.count().toFlowable().subscribe(w); // we should be called only once verify(w).onNext(4L); @@ -123,8 +126,8 @@ public void testCountAFewItemsFlowable() { @Test public void testCountZeroItemsFlowable() { - Flowable<String> observable = Flowable.empty(); - observable.count().toFlowable().subscribe(w); + Flowable<String> flowable = Flowable.empty(); + flowable.count().toFlowable().subscribe(w); // we should be called only once verify(w).onNext(0L); verify(w, never()).onError(any(Throwable.class)); @@ -133,25 +136,24 @@ public void testCountZeroItemsFlowable() { @Test public void testCountErrorFlowable() { - Flowable<String> o = Flowable.error(new Callable<Throwable>() { + Flowable<String> f = Flowable.error(new Callable<Throwable>() { @Override public Throwable call() { return new RuntimeException(); } }); - o.count().toFlowable().subscribe(w); + f.count().toFlowable().subscribe(w); verify(w, never()).onNext(anyInt()); verify(w, never()).onComplete(); verify(w, times(1)).onError(any(RuntimeException.class)); } - @Test public void testCountAFewItems() { - Flowable<String> observable = Flowable.just("a", "b", "c", "d"); + Flowable<String> flowable = Flowable.just("a", "b", "c", "d"); - observable.count().subscribe(wo); + flowable.count().subscribe(wo); // we should be called only once verify(wo).onSuccess(4L); @@ -160,8 +162,8 @@ public void testCountAFewItems() { @Test public void testCountZeroItems() { - Flowable<String> observable = Flowable.empty(); - observable.count().subscribe(wo); + Flowable<String> flowable = Flowable.empty(); + flowable.count().subscribe(wo); // we should be called only once verify(wo).onSuccess(0L); verify(wo, never()).onError(any(Throwable.class)); @@ -169,22 +171,22 @@ public void testCountZeroItems() { @Test public void testCountError() { - Flowable<String> o = Flowable.error(new Callable<Throwable>() { + Flowable<String> f = Flowable.error(new Callable<Throwable>() { @Override public Throwable call() { return new RuntimeException(); } }); - o.count().subscribe(wo); + f.count().subscribe(wo); verify(wo, never()).onSuccess(anyInt()); verify(wo, times(1)).onError(any(RuntimeException.class)); } @Test public void testTakeFirstWithPredicateOfSome() { - Flowable<Integer> observable = Flowable.just(1, 3, 5, 4, 6, 3); - observable.filter(IS_EVEN).take(1).subscribe(w); + Flowable<Integer> flowable = Flowable.just(1, 3, 5, 4, 6, 3); + flowable.filter(IS_EVEN).take(1).subscribe(w); verify(w, times(1)).onNext(anyInt()); verify(w).onNext(4); verify(w, times(1)).onComplete(); @@ -193,8 +195,8 @@ public void testTakeFirstWithPredicateOfSome() { @Test public void testTakeFirstWithPredicateOfNoneMatchingThePredicate() { - Flowable<Integer> observable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); - observable.filter(IS_EVEN).take(1).subscribe(w); + Flowable<Integer> flowable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); + flowable.filter(IS_EVEN).take(1).subscribe(w); verify(w, never()).onNext(anyInt()); verify(w, times(1)).onComplete(); verify(w, never()).onError(any(Throwable.class)); @@ -202,8 +204,8 @@ public void testTakeFirstWithPredicateOfNoneMatchingThePredicate() { @Test public void testTakeFirstOfSome() { - Flowable<Integer> observable = Flowable.just(1, 2, 3); - observable.take(1).subscribe(w); + Flowable<Integer> flowable = Flowable.just(1, 2, 3); + flowable.take(1).subscribe(w); verify(w, times(1)).onNext(anyInt()); verify(w).onNext(1); verify(w, times(1)).onComplete(); @@ -212,8 +214,8 @@ public void testTakeFirstOfSome() { @Test public void testTakeFirstOfNone() { - Flowable<Integer> observable = Flowable.empty(); - observable.take(1).subscribe(w); + Flowable<Integer> flowable = Flowable.empty(); + flowable.take(1).subscribe(w); verify(w, never()).onNext(anyInt()); verify(w, times(1)).onComplete(); verify(w, never()).onError(any(Throwable.class)); @@ -221,8 +223,8 @@ public void testTakeFirstOfNone() { @Test public void testFirstOfNoneFlowable() { - Flowable<Integer> observable = Flowable.empty(); - observable.firstElement().toFlowable().subscribe(w); + Flowable<Integer> flowable = Flowable.empty(); + flowable.firstElement().toFlowable().subscribe(w); verify(w, never()).onNext(anyInt()); verify(w).onComplete(); verify(w, never()).onError(any(Throwable.class)); @@ -230,8 +232,8 @@ public void testFirstOfNoneFlowable() { @Test public void testFirstWithPredicateOfNoneMatchingThePredicateFlowable() { - Flowable<Integer> observable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); - observable.filter(IS_EVEN).firstElement().toFlowable().subscribe(w); + Flowable<Integer> flowable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); + flowable.filter(IS_EVEN).firstElement().toFlowable().subscribe(w); verify(w, never()).onNext(anyInt()); verify(w).onComplete(); verify(w, never()).onError(any(Throwable.class)); @@ -239,8 +241,8 @@ public void testFirstWithPredicateOfNoneMatchingThePredicateFlowable() { @Test public void testFirstOfNone() { - Flowable<Integer> observable = Flowable.empty(); - observable.firstElement().subscribe(wm); + Flowable<Integer> flowable = Flowable.empty(); + flowable.firstElement().subscribe(wm); verify(wm, never()).onSuccess(anyInt()); verify(wm).onComplete(); verify(wm, never()).onError(isA(NoSuchElementException.class)); @@ -248,8 +250,8 @@ public void testFirstOfNone() { @Test public void testFirstWithPredicateOfNoneMatchingThePredicate() { - Flowable<Integer> observable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); - observable.filter(IS_EVEN).firstElement().subscribe(wm); + Flowable<Integer> flowable = Flowable.just(1, 3, 5, 7, 9, 7, 5, 3, 1); + flowable.filter(IS_EVEN).firstElement().subscribe(wm); verify(wm, never()).onSuccess(anyInt()); verify(wm, times(1)).onComplete(); verify(wm, never()).onError(isA(NoSuchElementException.class)); @@ -257,8 +259,8 @@ public void testFirstWithPredicateOfNoneMatchingThePredicate() { @Test public void testReduce() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4); - observable.reduce(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4); + flowable.reduce(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -273,8 +275,8 @@ public Integer apply(Integer t1, Integer t2) { @Test public void testReduceWithEmptyObservable() { - Flowable<Integer> observable = Flowable.range(1, 0); - observable.reduce(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> flowable = Flowable.range(1, 0); + flowable.reduce(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -292,8 +294,8 @@ public Integer apply(Integer t1, Integer t2) { */ @Test public void testReduceWithEmptyObservableAndSeed() { - Flowable<Integer> observable = Flowable.range(1, 0); - int value = observable.reduce(1, new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> flowable = Flowable.range(1, 0); + int value = flowable.reduce(1, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -306,8 +308,8 @@ public Integer apply(Integer t1, Integer t2) { @Test public void testReduceWithInitialValue() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4); - observable.reduce(50, new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4); + flowable.reduce(50, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -322,32 +324,34 @@ public Integer apply(Integer t1, Integer t2) { @Ignore("Throwing is not allowed from the unsafeCreate?!") @Test // FIXME throwing is not allowed from the create?! public void testOnSubscribeFails() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final RuntimeException re = new RuntimeException("bad impl"); - Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> s) { throw re; } }); - o.subscribe(observer); - verify(observer, times(0)).onNext(anyString()); - verify(observer, times(0)).onComplete(); - verify(observer, times(1)).onError(re); + f.subscribe(subscriber); + + verify(subscriber, times(0)).onNext(anyString()); + verify(subscriber, times(0)).onComplete(); + verify(subscriber, times(1)).onError(re); } @Test public void testMaterializeDematerializeChaining() { Flowable<Integer> obs = Flowable.just(1); - Flowable<Integer> chained = obs.materialize().dematerialize(); + Flowable<Integer> chained = obs.materialize() + .dematerialize(Functions.<Notification<Integer>>identity()); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - chained.subscribe(observer); + chained.subscribe(subscriber); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onComplete(); - verify(observer, times(0)).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(0)).onError(any(Throwable.class)); } /** @@ -493,15 +497,15 @@ public void testPublishLast() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); ConnectableFlowable<String> connectable = Flowable.<String>unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); count.incrementAndGet(); new Thread(new Runnable() { @Override public void run() { - observer.onNext("first"); - observer.onNext("last"); - observer.onComplete(); + subscriber.onNext("first"); + subscriber.onNext("last"); + subscriber.onComplete(); } }).start(); } @@ -529,31 +533,31 @@ public void accept(String value) { @Test public void testReplay() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableFlowable<String> o = Flowable.<String>unsafeCreate(new Publisher<String>() { + ConnectableFlowable<String> f = Flowable.<String>unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } }).replay(); // we connect immediately and it will emit the value - Disposable s = o.connect(); + Disposable connection = f.connect(); try { // we then expect the following 2 subscriptions to get that same value final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -562,7 +566,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -575,23 +579,23 @@ public void accept(String v) { } assertEquals(1, counter.get()); } finally { - s.dispose(); + connection.dispose(); } } @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Flowable<String> o = Flowable.<String>unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.<String>unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } @@ -601,7 +605,7 @@ public void run() { final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -610,7 +614,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -627,16 +631,16 @@ public void accept(String v) { @Test public void testCacheWithCapacity() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Flowable<String> o = Flowable.<String>unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.<String>unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } @@ -646,7 +650,7 @@ public void run() { final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -655,7 +659,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -708,13 +712,13 @@ public void testErrorThrownWithoutErrorHandlerAsynchronous() throws InterruptedE final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(final Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { try { - observer.onError(new Error("failure")); + subscriber.onError(new Error("failure")); } catch (Throwable e) { // without an onError handler it has to just throw on whatever thread invokes it exception.set(e); @@ -767,19 +771,18 @@ public void onNext(String v) { @Test public void testOfType() { - Flowable<String> observable = Flowable.just(1, "abc", false, 2L).ofType(String.class); + Flowable<String> flowable = Flowable.just(1, "abc", false, 2L).ofType(String.class); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(1); - verify(observer, times(1)).onNext("abc"); - verify(observer, never()).onNext(false); - verify(observer, never()).onNext(2L); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(1); + verify(subscriber, times(1)).onNext("abc"); + verify(subscriber, never()).onNext(false); + verify(subscriber, never()).onNext(2L); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -790,89 +793,83 @@ public void testOfTypeWithPolymorphism() { l2.add(2); @SuppressWarnings("rawtypes") - Flowable<List> observable = Flowable.<Object> just(l1, l2, "123").ofType(List.class); + Flowable<List> flowable = Flowable.<Object> just(l1, l2, "123").ofType(List.class); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(l1); - verify(observer, times(1)).onNext(l2); - verify(observer, never()).onNext("123"); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(l1); + verify(subscriber, times(1)).onNext(l2); + verify(subscriber, never()).onNext("123"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testContainsFlowable() { - Flowable<Boolean> observable = Flowable.just("a", "b", "c").contains("b").toFlowable(); + Flowable<Boolean> flowable = Flowable.just("a", "b", "c").contains("b").toFlowable(); - FlowableSubscriber<Boolean> observer = TestHelper.mockSubscriber(); + FlowableSubscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onNext(false); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onNext(false); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testContainsWithInexistenceFlowable() { - Flowable<Boolean> observable = Flowable.just("a", "b").contains("c").toFlowable(); + Flowable<Boolean> flowable = Flowable.just("a", "b").contains("c").toFlowable(); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onNext(true); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @Ignore("null values are not allowed") public void testContainsWithNullFlowable() { - Flowable<Boolean> observable = Flowable.just("a", "b", null).contains(null).toFlowable(); + Flowable<Boolean> flowable = Flowable.just("a", "b", null).contains(null).toFlowable(); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onNext(false); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onNext(false); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testContainsWithEmptyObservableFlowable() { - Flowable<Boolean> observable = Flowable.<String> empty().contains("a").toFlowable(); + Flowable<Boolean> flowable = Flowable.<String> empty().contains("a").toFlowable(); - FlowableSubscriber<Object> observer = TestHelper.mockSubscriber(); + FlowableSubscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onNext(true); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } - @Test public void testContains() { - Single<Boolean> observable = Flowable.just("a", "b", "c").contains("b"); // FIXME nulls not allowed, changed to "c" + Single<Boolean> single = Flowable.just("a", "b", "c").contains("b"); // FIXME nulls not allowed, changed to "c" SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(true); verify(observer, never()).onSuccess(false); @@ -882,11 +879,11 @@ public void testContains() { @Test public void testContainsWithInexistence() { - Single<Boolean> observable = Flowable.just("a", "b").contains("c"); // FIXME null values are not allowed, removed + Single<Boolean> single = Flowable.just("a", "b").contains("c"); // FIXME null values are not allowed, removed SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -897,11 +894,11 @@ public void testContainsWithInexistence() { @Test @Ignore("null values are not allowed") public void testContainsWithNull() { - Single<Boolean> observable = Flowable.just("a", "b", null).contains(null); + Single<Boolean> single = Flowable.just("a", "b", null).contains(null); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(true); verify(observer, never()).onSuccess(false); @@ -911,11 +908,11 @@ public void testContainsWithNull() { @Test public void testContainsWithEmptyObservable() { - Single<Boolean> observable = Flowable.<String> empty().contains("a"); + Single<Boolean> single = Flowable.<String> empty().contains("a"); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -925,24 +922,24 @@ public void testContainsWithEmptyObservable() { @Test public void testIgnoreElementsFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3).ignoreElements().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1, 2, 3).ignoreElements().toFlowable(); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(any(Integer.class)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(any(Integer.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testIgnoreElements() { - Completable observable = Flowable.just(1, 2, 3).ignoreElements(); + Completable completable = Flowable.just(1, 2, 3).ignoreElements(); CompletableObserver observer = TestHelper.mockCompletableObserver(); - observable.subscribe(observer); + completable.subscribe(observer); verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); @@ -951,58 +948,58 @@ public void testIgnoreElements() { @Test public void testJustWithScheduler() { TestScheduler scheduler = new TestScheduler(); - Flowable<Integer> observable = Flowable.fromArray(1, 2).subscribeOn(scheduler); + Flowable<Integer> flowable = Flowable.fromArray(1, 2).subscribeOn(scheduler); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testStartWithWithScheduler() { TestScheduler scheduler = new TestScheduler(); - Flowable<Integer> observable = Flowable.just(3, 4).startWith(Arrays.asList(1, 2)).subscribeOn(scheduler); + Flowable<Integer> flowable = Flowable.just(3, 4).startWith(Arrays.asList(1, 2)).subscribeOn(scheduler); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onNext(3); - inOrder.verify(observer, times(1)).onNext(4); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onNext(3); + inOrder.verify(subscriber, times(1)).onNext(4); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testRangeWithScheduler() { TestScheduler scheduler = new TestScheduler(); - Flowable<Integer> observable = Flowable.range(3, 4).subscribeOn(scheduler); + Flowable<Integer> flowable = Flowable.range(3, 4).subscribeOn(scheduler); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(3); - inOrder.verify(observer, times(1)).onNext(4); - inOrder.verify(observer, times(1)).onNext(5); - inOrder.verify(observer, times(1)).onNext(6); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(3); + inOrder.verify(subscriber, times(1)).onNext(4); + inOrder.verify(subscriber, times(1)).onNext(5); + inOrder.verify(subscriber, times(1)).onNext(6); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -1031,7 +1028,7 @@ public void testAmbWith() { public void testTakeWhileToList() { final int expectedCount = 3; final AtomicInteger count = new AtomicInteger(); - for (int i = 0;i < expectedCount; i++) { + for (int i = 0; i < expectedCount; i++) { Flowable .just(Boolean.TRUE, Boolean.FALSE) .takeWhile(new Predicate<Boolean>() { @@ -1074,18 +1071,25 @@ public String apply(Integer v) { @Test public void testErrorThrownIssue1685() { - FlowableProcessor<Object> subject = ReplayProcessor.create(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + FlowableProcessor<Object> processor = ReplayProcessor.create(); + + Flowable.error(new RuntimeException("oops")) + .materialize() + .delay(1, TimeUnit.SECONDS) + .dematerialize(Functions.<Notification<Object>>identity()) + .subscribe(processor); - Flowable.error(new RuntimeException("oops")) - .materialize() - .delay(1, TimeUnit.SECONDS) - .dematerialize() - .subscribe(subject); + processor.subscribe(); + processor.materialize().blockingFirst(); - subject.subscribe(); - subject.materialize().blockingFirst(); + System.out.println("Done"); - System.out.println("Done"); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -1113,7 +1117,7 @@ public void testForEachWithNull() { public void testExtend() { final TestSubscriber<Object> subscriber = new TestSubscriber<Object>(); final Object value = new Object(); - Flowable.just(value).to(new Function<Flowable<Object>, Object>() { + Object returned = Flowable.just(value).to(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> onSubscribe) { onSubscribe.subscribe(subscriber); @@ -1123,6 +1127,36 @@ public Object apply(Flowable<Object> onSubscribe) { return subscriber.values().get(0); } }); + assertSame(returned, value); + } + + @Test + public void testAsExtend() { + final TestSubscriber<Object> subscriber = new TestSubscriber<Object>(); + final Object value = new Object(); + Object returned = Flowable.just(value).as(new FlowableConverter<Object, Object>() { + @Override + public Object apply(Flowable<Object> onSubscribe) { + onSubscribe.subscribe(subscriber); + subscriber.assertNoErrors(); + subscriber.assertComplete(); + subscriber.assertValue(value); + return subscriber.values().get(0); + } + }); + assertSame(returned, value); + } + + @Test + public void as() { + Flowable.just(1).as(new FlowableConverter<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Flowable<Integer> v) { + return v.toObservable(); + } + }) + .test() + .assertResult(1); } @Test diff --git a/src/test/java/io/reactivex/flowable/FlowableThrottleLastTests.java b/src/test/java/io/reactivex/flowable/FlowableThrottleLastTests.java index 2dfc9d373f..8d249e6c23 100644 --- a/src/test/java/io/reactivex/flowable/FlowableThrottleLastTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableThrottleLastTests.java @@ -29,11 +29,11 @@ public class FlowableThrottleLastTests { @Test public void testThrottle() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); TestScheduler s = new TestScheduler(); PublishProcessor<Integer> o = PublishProcessor.create(); - o.throttleLast(500, TimeUnit.MILLISECONDS, s).subscribe(observer); + o.throttleLast(500, TimeUnit.MILLISECONDS, s).subscribe(subscriber); // send events with simulated time increments s.advanceTimeTo(0, TimeUnit.MILLISECONDS); @@ -51,11 +51,11 @@ public void testThrottle() { s.advanceTimeTo(1501, TimeUnit.MILLISECONDS); o.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onNext(2); - inOrder.verify(observer).onNext(6); - inOrder.verify(observer).onNext(7); - inOrder.verify(observer).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(6); + inOrder.verify(subscriber).onNext(7); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } } diff --git a/src/test/java/io/reactivex/flowable/FlowableThrottleWithTimeoutTests.java b/src/test/java/io/reactivex/flowable/FlowableThrottleWithTimeoutTests.java index 4635332b3c..339d7f4316 100644 --- a/src/test/java/io/reactivex/flowable/FlowableThrottleWithTimeoutTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableThrottleWithTimeoutTests.java @@ -29,12 +29,12 @@ public class FlowableThrottleWithTimeoutTests { @Test public void testThrottle() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); TestScheduler s = new TestScheduler(); PublishProcessor<Integer> o = PublishProcessor.create(); o.throttleWithTimeout(500, TimeUnit.MILLISECONDS, s) - .subscribe(observer); + .subscribe(subscriber); // send events with simulated time increments s.advanceTimeTo(0, TimeUnit.MILLISECONDS); @@ -52,11 +52,11 @@ public void testThrottle() { s.advanceTimeTo(1800, TimeUnit.MILLISECONDS); o.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onNext(2); - inOrder.verify(observer).onNext(6); - inOrder.verify(observer).onNext(7); - inOrder.verify(observer).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(6); + inOrder.verify(subscriber).onNext(7); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } diff --git a/src/test/java/io/reactivex/flowable/FlowableWindowTests.java b/src/test/java/io/reactivex/flowable/FlowableWindowTests.java index 9ef4211aa4..08151bcb8b 100644 --- a/src/test/java/io/reactivex/flowable/FlowableWindowTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableWindowTests.java @@ -16,11 +16,15 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.Test; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.functions.*; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subscribers.TestSubscriber; public class FlowableWindowTests { @@ -50,4 +54,43 @@ public void accept(List<Integer> xs) { assertEquals(2, lists.size()); } + + @Test + public void timeSizeWindowAlternatingBounds() { + TestScheduler scheduler = new TestScheduler(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = pp.window(5, TimeUnit.SECONDS, scheduler, 2) + .flatMapSingle(new Function<Flowable<Integer>, SingleSource<List<Integer>>>() { + @Override + public SingleSource<List<Integer>> apply(Flowable<Integer> v) { + return v.toList(); + } + }) + .test(); + + pp.onNext(1); + pp.onNext(2); + ts.assertValueCount(1); // size bound hit + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + pp.onNext(3); + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + ts.assertValueCount(2); // time bound hit + + pp.onNext(4); + pp.onNext(5); + + ts.assertValueCount(3); // size bound hit again + + pp.onNext(4); + + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + + ts.assertValueCount(4) + .assertNoErrors() + .assertNotComplete(); + + ts.cancel(); + } } diff --git a/src/test/java/io/reactivex/flowable/FlowableZipTests.java b/src/test/java/io/reactivex/flowable/FlowableZipTests.java index 78ffa8cee5..d901d36bc2 100644 --- a/src/test/java/io/reactivex/flowable/FlowableZipTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableZipTests.java @@ -129,7 +129,6 @@ public void accept(ExtendedResult t1) { } }; - @Test public void zipWithDelayError() { Flowable.just(1) diff --git a/src/test/java/io/reactivex/internal/SubscribeWithTest.java b/src/test/java/io/reactivex/internal/SubscribeWithTest.java index e2c23b51d5..942762009c 100644 --- a/src/test/java/io/reactivex/internal/SubscribeWithTest.java +++ b/src/test/java/io/reactivex/internal/SubscribeWithTest.java @@ -30,7 +30,6 @@ public void withFlowable() { .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } - @Test public void withObservable() { Observable.range(1, 10) @@ -38,7 +37,6 @@ public void withObservable() { .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } - class ObserverImpl implements SingleObserver<Object>, CompletableObserver { Object value; diff --git a/src/test/java/io/reactivex/internal/disposables/ArrayCompositeDisposableTest.java b/src/test/java/io/reactivex/internal/disposables/ArrayCompositeDisposableTest.java index 77afd9e6b1..81fb4de489 100644 --- a/src/test/java/io/reactivex/internal/disposables/ArrayCompositeDisposableTest.java +++ b/src/test/java/io/reactivex/internal/disposables/ArrayCompositeDisposableTest.java @@ -19,7 +19,6 @@ import io.reactivex.TestHelper; import io.reactivex.disposables.*; -import io.reactivex.schedulers.Schedulers; public class ArrayCompositeDisposableTest { @@ -70,7 +69,7 @@ public void normal() { @Test public void disposeRace() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ArrayCompositeDisposable acd = new ArrayCompositeDisposable(2); Runnable r = new Runnable() { @@ -80,13 +79,13 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } @Test public void replaceRace() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ArrayCompositeDisposable acd = new ArrayCompositeDisposable(2); Runnable r = new Runnable() { @@ -96,13 +95,13 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } @Test public void setRace() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ArrayCompositeDisposable acd = new ArrayCompositeDisposable(2); Runnable r = new Runnable() { @@ -112,7 +111,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } diff --git a/src/test/java/io/reactivex/internal/disposables/CancellableDisposableTest.java b/src/test/java/io/reactivex/internal/disposables/CancellableDisposableTest.java index aedb7a716b..41e3997243 100644 --- a/src/test/java/io/reactivex/internal/disposables/CancellableDisposableTest.java +++ b/src/test/java/io/reactivex/internal/disposables/CancellableDisposableTest.java @@ -24,7 +24,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Cancellable; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class CancellableDisposableTest { @@ -84,7 +83,7 @@ public void cancel() throws Exception { @Test public void disposeRace() { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicInteger count = new AtomicInteger(); Cancellable c = new Cancellable() { @@ -103,7 +102,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); assertEquals(1, count.get()); } diff --git a/src/test/java/io/reactivex/internal/disposables/DisposableHelperTest.java b/src/test/java/io/reactivex/internal/disposables/DisposableHelperTest.java index 941e082101..b219e44dd1 100644 --- a/src/test/java/io/reactivex/internal/disposables/DisposableHelperTest.java +++ b/src/test/java/io/reactivex/internal/disposables/DisposableHelperTest.java @@ -23,7 +23,6 @@ import io.reactivex.TestHelper; import io.reactivex.disposables.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class DisposableHelperTest { @Test @@ -53,7 +52,7 @@ public void validationNull() { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicReference<Disposable> d = new AtomicReference<Disposable>(); Runnable r = new Runnable() { @@ -63,13 +62,13 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } @Test public void setReplace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicReference<Disposable> d = new AtomicReference<Disposable>(); Runnable r = new Runnable() { @@ -79,13 +78,13 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } @Test public void setRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicReference<Disposable> d = new AtomicReference<Disposable>(); Runnable r = new Runnable() { @@ -95,7 +94,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.io()); + TestHelper.race(r, r); } } diff --git a/src/test/java/io/reactivex/internal/disposables/EmptyDisposableTest.java b/src/test/java/io/reactivex/internal/disposables/EmptyDisposableTest.java index 7e25cbcd4f..d373560413 100644 --- a/src/test/java/io/reactivex/internal/disposables/EmptyDisposableTest.java +++ b/src/test/java/io/reactivex/internal/disposables/EmptyDisposableTest.java @@ -14,10 +14,11 @@ package io.reactivex.internal.disposables; import static org.junit.Assert.*; + import org.junit.Test; import io.reactivex.TestHelper; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; public class EmptyDisposableTest { @@ -28,8 +29,8 @@ public void noOffer() { @Test public void asyncFusion() { - assertEquals(QueueDisposable.NONE, EmptyDisposable.INSTANCE.requestFusion(QueueDisposable.SYNC)); - assertEquals(QueueDisposable.ASYNC, EmptyDisposable.INSTANCE.requestFusion(QueueDisposable.ASYNC)); + assertEquals(QueueFuseable.NONE, EmptyDisposable.INSTANCE.requestFusion(QueueFuseable.SYNC)); + assertEquals(QueueFuseable.ASYNC, EmptyDisposable.INSTANCE.requestFusion(QueueFuseable.ASYNC)); } @Test diff --git a/src/test/java/io/reactivex/internal/disposables/ListCompositeDisposableTest.java b/src/test/java/io/reactivex/internal/disposables/ListCompositeDisposableTest.java index fa6050d952..4d3ed24708 100644 --- a/src/test/java/io/reactivex/internal/disposables/ListCompositeDisposableTest.java +++ b/src/test/java/io/reactivex/internal/disposables/ListCompositeDisposableTest.java @@ -22,7 +22,6 @@ import io.reactivex.TestHelper; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; -import io.reactivex.schedulers.Schedulers; public class ListCompositeDisposableTest { @@ -179,7 +178,7 @@ public void remove() { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); Runnable run = new Runnable() { @@ -189,13 +188,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); Runnable run = new Runnable() { @@ -205,13 +204,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addAllRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); Runnable run = new Runnable() { @@ -221,13 +220,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void removeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -241,13 +240,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void deleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -261,13 +260,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void clearRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -281,13 +280,13 @@ public void run() { } }; - TestHelper.race(run, run, Schedulers.io()); + TestHelper.race(run, run); } } @Test public void addDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); Runnable run = new Runnable() { @@ -304,13 +303,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void addAllDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); Runnable run = new Runnable() { @@ -327,13 +326,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void removeDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -354,13 +353,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void deleteDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -381,13 +380,13 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } @Test public void clearDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ListCompositeDisposable cd = new ListCompositeDisposable(); final Disposable d1 = Disposables.empty(); @@ -408,7 +407,7 @@ public void run() { } }; - TestHelper.race(run, run2, Schedulers.io()); + TestHelper.race(run, run2); } } } diff --git a/src/test/java/io/reactivex/internal/disposables/ObserverFullArbiterTest.java b/src/test/java/io/reactivex/internal/disposables/ObserverFullArbiterTest.java deleted file mode 100644 index 48d3507d7c..0000000000 --- a/src/test/java/io/reactivex/internal/disposables/ObserverFullArbiterTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.disposables; - -import static org.junit.Assert.*; - -import java.util.List; - -import org.junit.Test; - -import io.reactivex.TestHelper; -import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.disposables.ObserverFullArbiter; -import io.reactivex.internal.util.NotificationLite; -import io.reactivex.observers.TestObserver; -import io.reactivex.plugins.RxJavaPlugins; - -public class ObserverFullArbiterTest { - - @Test - public void setSubscriptionAfterCancel() { - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(new TestObserver<Integer>(), null, 128); - - fa.dispose(); - - Disposable bs = Disposables.empty(); - - assertFalse(fa.setDisposable(bs)); - - assertFalse(fa.setDisposable(null)); - } - - @Test - public void cancelAfterPoll() { - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(new TestObserver<Integer>(), null, 128); - - Disposable bs = Disposables.empty(); - - fa.queue.offer(fa.s, NotificationLite.disposable(bs)); - - assertFalse(fa.isDisposed()); - - fa.dispose(); - - assertTrue(fa.isDisposed()); - - fa.drain(); - - assertTrue(bs.isDisposed()); - } - - @Test - public void errorAfterCancel() { - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(new TestObserver<Integer>(), null, 128); - - Disposable bs = Disposables.empty(); - - fa.dispose(); - - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - fa.onError(new TestException(), bs); - - TestHelper.assertUndeliverable(errors, 0, TestException.class); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void cancelAfterError() { - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(new TestObserver<Integer>(), null, 128); - - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - fa.queue.offer(fa.s, NotificationLite.error(new TestException())); - - fa.dispose(); - - fa.drain(); - TestHelper.assertUndeliverable(errors, 0, TestException.class); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void offerDifferentSubscription() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(ts, null, 128); - - Disposable bs = Disposables.empty(); - - fa.queue.offer(bs, NotificationLite.next(1)); - - fa.drain(); - - ts.assertNoValues(); - } - - @Test - public void dontEnterDrain() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - - ObserverFullArbiter<Integer> fa = new ObserverFullArbiter<Integer>(ts, null, 128); - - fa.queue.offer(fa.s, NotificationLite.next(1)); - - fa.wip.getAndIncrement(); - - fa.drain(); - - ts.assertNoValues(); - } -} diff --git a/src/test/java/io/reactivex/internal/functions/ObjectHelperTest.java b/src/test/java/io/reactivex/internal/functions/ObjectHelperTest.java index aefbfea49c..898900d34f 100644 --- a/src/test/java/io/reactivex/internal/functions/ObjectHelperTest.java +++ b/src/test/java/io/reactivex/internal/functions/ObjectHelperTest.java @@ -58,4 +58,17 @@ public void compare() { assertEquals(0, ObjectHelper.compare(0, 0)); assertEquals(1, ObjectHelper.compare(2, 0)); } + + @Test + public void compareLong() { + assertEquals(-1, ObjectHelper.compare(0L, 2L)); + assertEquals(0, ObjectHelper.compare(0L, 0L)); + assertEquals(1, ObjectHelper.compare(2L, 0L)); + } + + @SuppressWarnings("deprecation") + @Test(expected = InternalError.class) + public void requireNonNullPrimitive() { + ObjectHelper.requireNonNull(0, "value"); + } } diff --git a/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java b/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java index 93aef221b2..797ae8e07a 100644 --- a/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/BasicFuseableObserverTest.java @@ -30,13 +30,16 @@ public void offer() { public Integer poll() throws Exception { return null; } + @Override public int requestFusion(int mode) { return 0; } + @Override public void onNext(Integer value) { } + @Override protected boolean beforeDownstream() { return false; @@ -58,10 +61,12 @@ public void offer2() { public Integer poll() throws Exception { return null; } + @Override public int requestFusion(int mode) { return 0; } + @Override public void onNext(Integer value) { } diff --git a/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java new file mode 100644 index 0000000000..6f69e1fd73 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/BlockingFirstObserverTest.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; + +public class BlockingFirstObserverTest { + + @Test + public void firstValueOnly() { + BlockingFirstObserver<Integer> bf = new BlockingFirstObserver<Integer>(); + Disposable d = Disposables.empty(); + bf.onSubscribe(d); + + bf.onNext(1); + + assertTrue(d.isDisposed()); + + assertEquals(1, bf.value.intValue()); + assertEquals(0, bf.getCount()); + + bf.onNext(2); + + assertEquals(1, bf.value.intValue()); + assertEquals(0, bf.getCount()); + + bf.onError(new TestException()); + assertEquals(1, bf.value.intValue()); + assertNull(bf.error); + assertEquals(0, bf.getCount()); + } +} diff --git a/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java new file mode 100644 index 0000000000..793253504b --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/BlockingMultiObserverTest.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.Test; + +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.schedulers.Schedulers; + +public class BlockingMultiObserverTest { + + @Test + public void dispose() { + BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + bmo.dispose(); + + Disposable d = Disposables.empty(); + + bmo.onSubscribe(d); + } + + @Test + public void blockingGetDefault() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + bmo.onSuccess(1); + } + }, 100, TimeUnit.MILLISECONDS); + + assertEquals(1, bmo.blockingGet(0).intValue()); + } + + @Test + public void blockingAwait() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + bmo.onSuccess(1); + } + }, 100, TimeUnit.MILLISECONDS); + + assertTrue(bmo.blockingAwait(1, TimeUnit.MINUTES)); + } + + @Test + public void blockingGetDefaultInterrupt() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Thread.currentThread().interrupt(); + try { + bmo.blockingGet(0); + fail("Should have thrown"); + } catch (RuntimeException ex) { + assertTrue(ex.getCause() instanceof InterruptedException); + } finally { + Thread.interrupted(); + } + } + + @Test + public void blockingGetErrorInterrupt() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Thread.currentThread().interrupt(); + try { + assertTrue(bmo.blockingGetError() instanceof InterruptedException); + } finally { + Thread.interrupted(); + } + } + + @Test + public void blockingGetErrorTimeoutInterrupt() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Thread.currentThread().interrupt(); + try { + bmo.blockingGetError(1, TimeUnit.MINUTES); + fail("Should have thrown"); + } catch (RuntimeException ex) { + assertTrue(ex.getCause() instanceof InterruptedException); + } finally { + Thread.interrupted(); + } + } + + @Test + public void blockingGetErrorDelayed() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + bmo.onError(new TestException()); + } + }, 100, TimeUnit.MILLISECONDS); + + assertTrue(bmo.blockingGetError() instanceof TestException); + } + + @Test + public void blockingGetErrorTimeoutDelayed() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + bmo.onError(new TestException()); + } + }, 100, TimeUnit.MILLISECONDS); + + assertTrue(bmo.blockingGetError(1, TimeUnit.MINUTES) instanceof TestException); + } + + @Test + public void blockingGetErrorTimedOut() { + final BlockingMultiObserver<Integer> bmo = new BlockingMultiObserver<Integer>(); + + try { + assertNull(bmo.blockingGetError(1, TimeUnit.NANOSECONDS)); + fail("Should have thrown"); + } catch (RuntimeException expected) { + assertEquals(TimeoutException.class, expected.getCause().getClass()); + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getCause().getMessage()); + } + } +} diff --git a/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java b/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java new file mode 100644 index 0000000000..72f2ab6bd6 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/BlockingObserverTest.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Test; + +public class BlockingObserverTest { + + @Test + public void dispose() { + Queue<Object> q = new ArrayDeque<Object>(); + + BlockingObserver<Object> bo = new BlockingObserver<Object>(q); + + bo.dispose(); + + assertEquals(BlockingObserver.TERMINATED, q.poll()); + + bo.dispose(); + + assertNull(q.poll()); + } +} diff --git a/src/test/java/io/reactivex/internal/observers/CallbackCompletableObserverTest.java b/src/test/java/io/reactivex/internal/observers/CallbackCompletableObserverTest.java new file mode 100644 index 0000000000..10b59b35d2 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/CallbackCompletableObserverTest.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import io.reactivex.internal.functions.Functions; +import org.junit.Test; + +import static org.junit.Assert.*; + +public final class CallbackCompletableObserverTest { + + @Test + public void emptyActionShouldReportNoCustomOnError() { + CallbackCompletableObserver o = new CallbackCompletableObserver(Functions.EMPTY_ACTION); + + assertFalse(o.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + CallbackCompletableObserver o = new CallbackCompletableObserver(Functions.<Throwable>emptyConsumer(), + Functions.EMPTY_ACTION); + + assertTrue(o.hasCustomOnError()); + } + +} diff --git a/src/test/java/io/reactivex/internal/observers/ConsumerSingleObserverTest.java b/src/test/java/io/reactivex/internal/observers/ConsumerSingleObserverTest.java new file mode 100644 index 0000000000..5499bf3243 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/ConsumerSingleObserverTest.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import io.reactivex.internal.functions.Functions; +import org.junit.Test; + +import static org.junit.Assert.*; + +public final class ConsumerSingleObserverTest { + + @Test + public void onErrorMissingShouldReportNoCustomOnError() { + ConsumerSingleObserver<Integer> o = new ConsumerSingleObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.ON_ERROR_MISSING); + + assertFalse(o.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + ConsumerSingleObserver<Integer> o = new ConsumerSingleObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.<Throwable>emptyConsumer()); + + assertTrue(o.hasCustomOnError()); + } + +} diff --git a/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java b/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java index ff0dc18984..7d2640b71c 100644 --- a/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/DeferredScalarObserverTest.java @@ -15,14 +15,16 @@ import static org.junit.Assert.*; +import java.util.List; + import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.QueueDisposable; -import io.reactivex.internal.observers.DeferredScalarObserver; +import io.reactivex.exceptions.*; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; public class DeferredScalarObserverTest { @@ -30,13 +32,13 @@ static final class TakeFirst extends DeferredScalarObserver<Integer, Integer> { private static final long serialVersionUID = -2793723002312330530L; - TakeFirst(Observer<? super Integer> actual) { - super(actual); + TakeFirst(Observer<? super Integer> downstream) { + super(downstream); } @Override public void onNext(Integer value) { - s.dispose(); + upstream.dispose(); complete(value); complete(value); } @@ -45,20 +47,27 @@ public void onNext(Integer value) { @Test public void normal() { - TestObserver<Integer> to = new TestObserver<Integer>(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = new TestObserver<Integer>(); - TakeFirst source = new TakeFirst(to); + TakeFirst source = new TakeFirst(to); - source.onSubscribe(Disposables.empty()); + source.onSubscribe(Disposables.empty()); - Disposable d = Disposables.empty(); - source.onSubscribe(d); + Disposable d = Disposables.empty(); + source.onSubscribe(d); - assertTrue(d.isDisposed()); + assertTrue(d.isDisposed()); - source.onNext(1); + source.onNext(1); - to.assertResult(1); + to.assertResult(1); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -106,59 +115,72 @@ public void dispose() { @Test public void fused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - TakeFirst source = new TakeFirst(to); + TakeFirst source = new TakeFirst(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - to.assertOf(ObserverFusion.<Integer>assertFuseable()); - to.assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)); + to.assertOf(ObserverFusion.<Integer>assertFuseable()); + to.assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)); - source.onNext(1); - source.onNext(1); - source.onError(new TestException()); - source.onComplete(); + source.onNext(1); + source.onNext(1); + source.onError(new TestException()); + source.onComplete(); - assertTrue(d.isDisposed()); + assertTrue(d.isDisposed()); - to.assertResult(1); + to.assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void fusedReject() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.SYNC); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); - TakeFirst source = new TakeFirst(to); + TakeFirst source = new TakeFirst(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - to.assertOf(ObserverFusion.<Integer>assertFuseable()); - to.assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.NONE)); + to.assertOf(ObserverFusion.<Integer>assertFuseable()); + to.assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)); - source.onNext(1); - source.onNext(1); - source.onError(new TestException()); - source.onComplete(); + source.onNext(1); + source.onNext(1); + source.onError(new TestException()); + source.onComplete(); - assertTrue(d.isDisposed()); + assertTrue(d.isDisposed()); - to.assertResult(1); + to.assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } static final class TakeLast extends DeferredScalarObserver<Integer, Integer> { private static final long serialVersionUID = -2793723002312330530L; - TakeLast(Observer<? super Integer> actual) { - super(actual); + TakeLast(Observer<? super Integer> downstream) { + super(downstream); } - @Override public void onNext(Integer value) { this.value = value; @@ -168,79 +190,107 @@ public void onNext(Integer value) { @Test public void nonfusedTerminateMore() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.NONE); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.NONE); - TakeLast source = new TakeLast(to); + TakeLast source = new TakeLast(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - source.onNext(1); - source.onComplete(); - source.onComplete(); - source.onError(new TestException()); + source.onNext(1); + source.onComplete(); + source.onComplete(); + source.onError(new TestException()); - to.assertResult(1); + to.assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void nonfusedError() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.NONE); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.NONE); - TakeLast source = new TakeLast(to); + TakeLast source = new TakeLast(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - source.onNext(1); - source.onError(new TestException()); - source.onError(new TestException()); - source.onComplete(); + source.onNext(1); + source.onError(new TestException()); + source.onError(new TestException("second")); + source.onComplete(); - to.assertFailure(TestException.class); + to.assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void fusedTerminateMore() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - TakeLast source = new TakeLast(to); + TakeLast source = new TakeLast(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - source.onNext(1); - source.onComplete(); - source.onComplete(); - source.onError(new TestException()); + source.onNext(1); + source.onComplete(); + source.onComplete(); + source.onError(new TestException()); - to.assertResult(1); + to.assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void fusedError() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - TakeLast source = new TakeLast(to); + TakeLast source = new TakeLast(to); - Disposable d = Disposables.empty(); + Disposable d = Disposables.empty(); - source.onSubscribe(d); + source.onSubscribe(d); - source.onNext(1); - source.onError(new TestException()); - source.onError(new TestException()); - source.onComplete(); + source.onNext(1); + source.onError(new TestException()); + source.onError(new TestException("second")); + source.onComplete(); - to.assertFailure(TestException.class); + to.assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void disposed() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.NONE); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.NONE); TakeLast source = new TakeLast(to); @@ -261,18 +311,18 @@ public void disposedAfterOnNext() { final TestObserver<Integer> to = new TestObserver<Integer>(); TakeLast source = new TakeLast(new Observer<Integer>() { - Disposable d; + Disposable upstream; @Override public void onSubscribe(Disposable d) { - this.d = d; + this.upstream = d; to.onSubscribe(d); } @Override public void onNext(Integer value) { to.onNext(value); - d.dispose(); + upstream.dispose(); } @Override @@ -295,7 +345,7 @@ public void onComplete() { @Test public void fusedEmpty() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); TakeLast source = new TakeLast(to); @@ -310,7 +360,7 @@ public void fusedEmpty() { @Test public void nonfusedEmpty() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.NONE); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.NONE); TakeLast source = new TakeLast(to); @@ -335,7 +385,7 @@ public void customFusion() { public void onSubscribe(Disposable d) { this.d = (QueueDisposable<Integer>)d; to.onSubscribe(d); - this.d.requestFusion(QueueDisposable.ANY); + this.d.requestFusion(QueueFuseable.ANY); } @Override @@ -385,7 +435,7 @@ public void customFusionClear() { public void onSubscribe(Disposable d) { this.d = (QueueDisposable<Integer>)d; to.onSubscribe(d); - this.d.requestFusion(QueueDisposable.ANY); + this.d.requestFusion(QueueFuseable.ANY); } @Override @@ -414,7 +464,7 @@ public void onComplete() { @Test public void offerThrow() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.NONE); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.NONE); TakeLast source = new TakeLast(to); @@ -433,7 +483,7 @@ public void customFusionDontConsume() { public void onSubscribe(Disposable d) { this.d = (QueueDisposable<Integer>)d; to.onSubscribe(d); - this.d.requestFusion(QueueDisposable.ANY); + this.d.requestFusion(QueueFuseable.ANY); } @Override diff --git a/src/test/java/io/reactivex/internal/observers/DisposableLambdaObserverTest.java b/src/test/java/io/reactivex/internal/observers/DisposableLambdaObserverTest.java new file mode 100644 index 0000000000..c152f15b37 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/DisposableLambdaObserverTest.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import io.reactivex.TestHelper; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public class DisposableLambdaObserverTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.doubleOnSubscribe(new DisposableLambdaObserver<Integer>( + new TestObserver<Integer>(), Functions.emptyConsumer(), Functions.EMPTY_ACTION + )); + } + + @Test + public void disposeCrash() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + DisposableLambdaObserver<Integer> o = new DisposableLambdaObserver<Integer>( + new TestObserver<Integer>(), Functions.emptyConsumer(), + new Action() { + @Override + public void run() throws Exception { + throw new TestException(); + } + } + ); + + o.onSubscribe(Disposables.empty()); + + assertFalse(o.isDisposed()); + + o.dispose(); + + assertTrue(o.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/observers/EmptyCompletableObserverTest.java b/src/test/java/io/reactivex/internal/observers/EmptyCompletableObserverTest.java new file mode 100644 index 0000000000..9e9085c41e --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/EmptyCompletableObserverTest.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; + +public final class EmptyCompletableObserverTest { + + @Test + public void defaultShouldReportNoCustomOnError() { + EmptyCompletableObserver o = new EmptyCompletableObserver(); + + assertFalse(o.hasCustomOnError()); + } +} diff --git a/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java b/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java index d31c4137fd..7ee71945bc 100644 --- a/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/FutureObserverTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.observers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.*; @@ -135,16 +136,16 @@ public void onSubscribe() throws Exception { try { - Disposable s = Disposables.empty(); + Disposable d1 = Disposables.empty(); - fo.onSubscribe(s); + fo.onSubscribe(d1); - Disposable s2 = Disposables.empty(); + Disposable d2 = Disposables.empty(); - fo.onSubscribe(s2); + fo.onSubscribe(d2); - assertFalse(s.isDisposed()); - assertTrue(s2.isDisposed()); + assertFalse(d1.isDisposed()); + assertTrue(d2.isDisposed()); TestHelper.assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } finally { @@ -154,7 +155,7 @@ public void onSubscribe() throws Exception { @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FutureSubscriber<Integer> fo = new FutureSubscriber<Integer>(); Runnable r = new Runnable() { @@ -164,7 +165,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @@ -185,7 +186,7 @@ public void run() { public void onErrorCancelRace() { RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); try { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FutureSubscriber<Integer> fo = new FutureSubscriber<Integer>(); final TestException ex = new TestException(); @@ -204,7 +205,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } finally { RxJavaPlugins.reset(); @@ -213,83 +214,115 @@ public void run() { @Test public void onCompleteCancelRace() { - for (int i = 0; i < 500; i++) { - final FutureSubscriber<Integer> fo = new FutureSubscriber<Integer>(); - - if (i % 3 == 0) { - fo.onSubscribe(new BooleanSubscription()); - } - - if (i % 2 == 0) { - fo.onNext(1); - } + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final FutureSubscriber<Integer> fo = new FutureSubscriber<Integer>(); - Runnable r1 = new Runnable() { - @Override - public void run() { - fo.cancel(false); + if (i % 3 == 0) { + fo.onSubscribe(new BooleanSubscription()); } - }; - Runnable r2 = new Runnable() { - @Override - public void run() { - fo.onComplete(); + if (i % 2 == 0) { + fo.onNext(1); } - }; - TestHelper.race(r1, r2, Schedulers.single()); + Runnable r1 = new Runnable() { + @Override + public void run() { + fo.cancel(false); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + fo.onComplete(); + } + }; + + TestHelper.race(r1, r2); + } + } finally { + RxJavaPlugins.reset(); } } @Test public void onErrorOnComplete() throws Exception { - fo.onError(new TestException("One")); - fo.onComplete(); - + List<Throwable> errors = TestHelper.trackPluginErrors(); try { - fo.get(5, TimeUnit.MILLISECONDS); - } catch (ExecutionException ex) { - assertTrue(ex.toString(), ex.getCause() instanceof TestException); - assertEquals("One", ex.getCause().getMessage()); + fo.onError(new TestException("One")); + fo.onComplete(); + + try { + fo.get(5, TimeUnit.MILLISECONDS); + } catch (ExecutionException ex) { + assertTrue(ex.toString(), ex.getCause() instanceof TestException); + assertEquals("One", ex.getCause().getMessage()); + } + + TestHelper.assertUndeliverable(errors, 0, NoSuchElementException.class); + } finally { + RxJavaPlugins.reset(); } } @Test public void onCompleteOnError() throws Exception { - fo.onComplete(); - fo.onError(new TestException("One")); - + List<Throwable> errors = TestHelper.trackPluginErrors(); try { - assertNull(fo.get(5, TimeUnit.MILLISECONDS)); - } catch (ExecutionException ex) { - assertTrue(ex.toString(), ex.getCause() instanceof NoSuchElementException); + fo.onComplete(); + fo.onError(new TestException("One")); + + try { + assertNull(fo.get(5, TimeUnit.MILLISECONDS)); + } catch (ExecutionException ex) { + assertTrue(ex.toString(), ex.getCause() instanceof NoSuchElementException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); } } @Test public void cancelOnError() throws Exception { - fo.cancel(true); - fo.onError(new TestException("One")); - + List<Throwable> errors = TestHelper.trackPluginErrors(); try { - fo.get(5, TimeUnit.MILLISECONDS); - fail("Should have thrown"); - } catch (CancellationException ex) { - // expected + fo.cancel(true); + fo.onError(new TestException("One")); + + try { + fo.get(5, TimeUnit.MILLISECONDS); + fail("Should have thrown"); + } catch (CancellationException ex) { + // expected + } + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); } } @Test public void cancelOnComplete() throws Exception { - fo.cancel(true); - fo.onComplete(); - + List<Throwable> errors = TestHelper.trackPluginErrors(); try { - fo.get(5, TimeUnit.MILLISECONDS); - fail("Should have thrown"); - } catch (CancellationException ex) { - // expected + fo.cancel(true); + fo.onComplete(); + + try { + fo.get(5, TimeUnit.MILLISECONDS); + fail("Should have thrown"); + } catch (CancellationException ex) { + // expected + } + + TestHelper.assertUndeliverable(errors, 0, NoSuchElementException.class); + } finally { + RxJavaPlugins.reset(); } } @@ -320,4 +353,14 @@ public void run() { assertEquals(1, fo.get().intValue()); } + + @Test + public void getTimedOut() throws Exception { + try { + fo.get(1, TimeUnit.NANOSECONDS); + fail("Should have thrown"); + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java b/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java index c463d48386..9cafad4569 100644 --- a/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/FutureSingleObserverTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.observers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.concurrent.*; @@ -22,7 +23,8 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.PublishSubject; public class FutureSingleObserverTest { @@ -66,7 +68,7 @@ public void cancel() { @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Future<?> f = Single.never().toFuture(); Runnable r = new Runnable() { @@ -76,7 +78,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @@ -88,8 +90,8 @@ public void timeout() throws Exception { try { f.get(100, TimeUnit.MILLISECONDS); fail("Should have thrown"); - } catch (TimeoutException ex) { - // expected + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(100, TimeUnit.MILLISECONDS), expected.getMessage()); } } @@ -130,7 +132,7 @@ public void getAwait() throws Exception { @Test public void onSuccessCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final Future<?> f = ps.single(-99).toFuture(); @@ -151,34 +153,39 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void onErrorCancelRace() { - for (int i = 0; i < 500; i++) { - final PublishSubject<Integer> ps = PublishSubject.create(); - - final Future<?> f = ps.single(-99).toFuture(); - - final TestException ex = new TestException(); - - Runnable r1 = new Runnable() { - @Override - public void run() { - f.cancel(true); - } - }; - - Runnable r2 = new Runnable() { - @Override - public void run() { - ps.onError(ex); - } - }; - - TestHelper.race(r1, r2, Schedulers.single()); + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Future<?> f = ps.single(-99).toFuture(); + + final TestException ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + f.cancel(true); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + TestHelper.race(r1, r2); + } + } finally { + RxJavaPlugins.reset(); } } } diff --git a/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java b/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java index d28a92be4b..81221cfc21 100644 --- a/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java +++ b/src/test/java/io/reactivex/internal/observers/LambdaObserverTest.java @@ -15,8 +15,10 @@ import static org.junit.Assert.*; +import java.io.IOException; import java.util.*; +import io.reactivex.internal.functions.Functions; import org.junit.Test; import io.reactivex.Observable; @@ -52,7 +54,7 @@ public void run() throws Exception { } }, new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException(); } }); @@ -89,7 +91,7 @@ public void run() throws Exception { } }, new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { } }); @@ -128,7 +130,7 @@ public void run() throws Exception { } }, new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { } }); @@ -174,7 +176,7 @@ public void run() throws Exception { } }, new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { } }); @@ -194,92 +196,107 @@ public void accept(Disposable s) throws Exception { @Test public void badSourceOnSubscribe() { - Observable<Integer> source = new Observable<Integer>() { - @Override - public void subscribeActual(Observer<? super Integer> s) { - Disposable s1 = Disposables.empty(); - s.onSubscribe(s1); - Disposable s2 = Disposables.empty(); - s.onSubscribe(s2); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable<Integer> source = new Observable<Integer>() { + @Override + public void subscribeActual(Observer<? super Integer> observer) { + Disposable d1 = Disposables.empty(); + observer.onSubscribe(d1); + Disposable d2 = Disposables.empty(); + observer.onSubscribe(d2); - assertFalse(s1.isDisposed()); - assertTrue(s2.isDisposed()); + assertFalse(d1.isDisposed()); + assertTrue(d2.isDisposed()); - s.onNext(1); - s.onComplete(); - } - }; + observer.onNext(1); + observer.onComplete(); + } + }; - final List<Object> received = new ArrayList<Object>(); + final List<Object> received = new ArrayList<Object>(); - LambdaObserver<Object> o = new LambdaObserver<Object>(new Consumer<Object>() { - @Override - public void accept(Object v) throws Exception { - received.add(v); - } - }, - new Consumer<Throwable>() { - @Override - public void accept(Throwable e) throws Exception { - received.add(e); - } - }, new Action() { - @Override - public void run() throws Exception { - received.add(100); - } - }, new Consumer<Disposable>() { - @Override - public void accept(Disposable s) throws Exception { - } - }); + LambdaObserver<Object> o = new LambdaObserver<Object>(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + received.add(v); + } + }, + new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + received.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(100); + } + }, new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + } + }); - source.subscribe(o); + source.subscribe(o); - assertEquals(Arrays.asList(1, 100), received); + assertEquals(Arrays.asList(1, 100), received); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } } + @Test public void badSourceEmitAfterDone() { - Observable<Integer> source = new Observable<Integer>() { - @Override - public void subscribeActual(Observer<? super Integer> s) { - s.onSubscribe(Disposables.empty()); - - s.onNext(1); - s.onComplete(); - s.onNext(2); - s.onError(new TestException()); - s.onComplete(); - } - }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable<Integer> source = new Observable<Integer>() { + @Override + public void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + }; - final List<Object> received = new ArrayList<Object>(); + final List<Object> received = new ArrayList<Object>(); - LambdaObserver<Object> o = new LambdaObserver<Object>(new Consumer<Object>() { - @Override - public void accept(Object v) throws Exception { - received.add(v); - } - }, - new Consumer<Throwable>() { - @Override - public void accept(Throwable e) throws Exception { - received.add(e); - } - }, new Action() { - @Override - public void run() throws Exception { - received.add(100); - } - }, new Consumer<Disposable>() { - @Override - public void accept(Disposable s) throws Exception { - } - }); + LambdaObserver<Object> o = new LambdaObserver<Object>(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + received.add(v); + } + }, + new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + received.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(100); + } + }, new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + } + }); + + source.subscribe(o); - source.subscribe(o); + assertEquals(Arrays.asList(1, 100), received); - assertEquals(Arrays.asList(1, 100), received); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -332,7 +349,7 @@ public void run() throws Exception { } }, new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException(); } }); @@ -342,4 +359,52 @@ public void accept(Disposable s) throws Exception { assertTrue(errors.toString(), errors.get(0) instanceof TestException); } + + @Test + public void onErrorMissingShouldReportNoCustomOnError() { + LambdaObserver<Integer> o = new LambdaObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION, + Functions.<Disposable>emptyConsumer()); + + assertFalse(o.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + LambdaObserver<Integer> o = new LambdaObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.<Throwable>emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.<Disposable>emptyConsumer()); + + assertTrue(o.hasCustomOnError()); + } + + @Test + public void disposedObserverShouldReportErrorOnGlobalErrorHandler() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final List<Throwable> observerErrors = Collections.synchronizedList(new ArrayList<Throwable>()); + + LambdaObserver<Integer> o = new LambdaObserver<Integer>(Functions.<Integer>emptyConsumer(), + new Consumer<Throwable>() { + @Override + public void accept(Throwable t) { + observerErrors.add(t); + } + }, + Functions.EMPTY_ACTION, + Functions.<Disposable>emptyConsumer()); + + o.dispose(); + o.onError(new IOException()); + o.onError(new IOException()); + + assertTrue(observerErrors.isEmpty()); + TestHelper.assertUndeliverable(errors, 0, IOException.class); + TestHelper.assertUndeliverable(errors, 1, IOException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/observers/QueueDrainObserverTest.java b/src/test/java/io/reactivex/internal/observers/QueueDrainObserverTest.java new file mode 100644 index 0000000000..1f9fffd356 --- /dev/null +++ b/src/test/java/io/reactivex/internal/observers/QueueDrainObserverTest.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.observers; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.observers.TestObserver; + +public class QueueDrainObserverTest { + + static final QueueDrainObserver<Integer, Integer, Integer> createUnordered(TestObserver<Integer> to, final Disposable d) { + return new QueueDrainObserver<Integer, Integer, Integer>(to, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathEmit(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Disposable d) { + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + } + }; + } + + static final QueueDrainObserver<Integer, Integer, Integer> createOrdered(TestObserver<Integer> to, final Disposable d) { + return new QueueDrainObserver<Integer, Integer, Integer>(to, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathOrderedEmit(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Disposable d) { + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + } + }; + } + + @Test + public void unorderedSlowPath() { + TestObserver<Integer> to = new TestObserver<Integer>(); + Disposable d = Disposables.empty(); + QueueDrainObserver<Integer, Integer, Integer> qd = createUnordered(to, d); + to.onSubscribe(Disposables.empty()); + + qd.enter(); + qd.onNext(1); + + to.assertEmpty(); + } + + @Test + public void orderedSlowPath() { + TestObserver<Integer> to = new TestObserver<Integer>(); + Disposable d = Disposables.empty(); + QueueDrainObserver<Integer, Integer, Integer> qd = createOrdered(to, d); + to.onSubscribe(Disposables.empty()); + + qd.enter(); + qd.onNext(1); + + to.assertEmpty(); + } + + @Test + public void orderedSlowPathNonEmptyQueue() { + TestObserver<Integer> to = new TestObserver<Integer>(); + Disposable d = Disposables.empty(); + QueueDrainObserver<Integer, Integer, Integer> qd = createOrdered(to, d); + to.onSubscribe(Disposables.empty()); + + qd.queue.offer(0); + qd.onNext(1); + + to.assertValuesOnly(0, 1); + } + + @Test + public void unorderedOnNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + TestObserver<Integer> to = new TestObserver<Integer>(); + Disposable d = Disposables.empty(); + final QueueDrainObserver<Integer, Integer, Integer> qd = createUnordered(to, d); + to.onSubscribe(Disposables.empty()); + + Runnable r1 = new Runnable() { + @Override + public void run() { + qd.onNext(1); + } + }; + + TestHelper.race(r1, r1); + + to.assertValuesOnly(1, 1); + } + } + + @Test + public void orderedOnNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + TestObserver<Integer> to = new TestObserver<Integer>(); + Disposable d = Disposables.empty(); + final QueueDrainObserver<Integer, Integer, Integer> qd = createOrdered(to, d); + to.onSubscribe(Disposables.empty()); + + Runnable r1 = new Runnable() { + @Override + public void run() { + qd.onNext(1); + } + }; + + TestHelper.race(r1, r1); + + to.assertValuesOnly(1, 1); + } + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java index 194ca4f119..f4a8a084b8 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAmbTest.java @@ -16,11 +16,17 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.completable.CompletableAmb.Amb; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -70,7 +76,7 @@ public void dispose() { @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -95,7 +101,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -110,7 +116,7 @@ public void run() { @Test public void nullSourceSuccessRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -134,7 +140,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (!errors.isEmpty()) { TestHelper.assertError(errors, 0, NullPointerException.class); @@ -163,4 +169,149 @@ public void ambArrayOrder() { Completable.ambArray(Completable.complete(), error).test().assertComplete(); } + @Test + public void ambRace() { + TestObserver<Void> to = new TestObserver<Void>(); + to.onSubscribe(Disposables.empty()); + + CompositeDisposable cd = new CompositeDisposable(); + AtomicBoolean once = new AtomicBoolean(); + Amb a = new Amb(once, cd, to); + a.onSubscribe(Disposables.empty()); + + a.onComplete(); + a.onComplete(); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + a.onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void untilCompletableMainComplete() { + CompletableSubject main = CompletableSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Void> to = main.ambWith(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilCompletableMainError() { + CompletableSubject main = CompletableSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Void> to = main.ambWith(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilCompletableOtherOnComplete() { + CompletableSubject main = CompletableSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Void> to = main.ambWith(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilCompletableOtherError() { + CompletableSubject main = CompletableSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Void> to = main.ambWith(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable.ambArray( + Completable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Completable.never() + ) + .subscribe(Functions.EMPTY_ACTION, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable.ambArray( + Completable.complete() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Completable.never() + ) + .subscribe(new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java new file mode 100644 index 0000000000..34b9c82436 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletableabTest.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; + +import static org.junit.Assert.*; + +public class CompletableAndThenCompletableabTest { + @Test(expected = NullPointerException.class) + public void andThenCompletableCompleteNull() { + Completable.complete() + .andThen((Completable) null); + } + + @Test + public void andThenCompletableCompleteComplete() { + Completable.complete() + .andThen(Completable.complete()) + .test() + .assertComplete(); + } + + @Test + public void andThenCompletableCompleteError() { + Completable.complete() + .andThen(Completable.error(new TestException("test"))) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("test"); + } + + @Test + public void andThenCompletableCompleteNever() { + Completable.complete() + .andThen(Completable.never()) + .test() + .assertNoValues() + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void andThenCompletableErrorComplete() { + Completable.error(new TestException("bla")) + .andThen(Completable.complete()) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("bla"); + } + + @Test + public void andThenCompletableErrorNever() { + Completable.error(new TestException("bla")) + .andThen(Completable.never()) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("bla"); + } + + @Test + public void andThenCompletableErrorError() { + Completable.error(new TestException("error1")) + .andThen(Completable.error(new TestException("error2"))) + .test() + .assertNotComplete() + .assertNoValues() + .assertError(TestException.class) + .assertErrorMessage("error1"); + } + + @Test + public void andThenCanceled() { + final AtomicInteger completableRunCount = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + completableRunCount.incrementAndGet(); + } + }) + .andThen(Completable.complete()) + .test(true) + .assertEmpty(); + assertEquals(1, completableRunCount.get()); + } + + @Test + public void andThenFirstCancels() { + final TestObserver<Void> to = new TestObserver<Void>(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + to.cancel(); + } + }) + .andThen(Completable.complete()) + .subscribe(to); + to + .assertNotComplete() + .assertNoErrors(); + } + + @Test + public void andThenSecondCancels() { + final TestObserver<Void> to = new TestObserver<Void>(); + Completable.complete() + .andThen(Completable.fromRunnable(new Runnable() { + @Override + public void run() { + to.cancel(); + } + })) + .subscribe(to); + to + .assertNotComplete() + .assertNoErrors(); + } + + @Test + public void andThenDisposed() { + TestHelper.checkDisposed(Completable.complete() + .andThen(Completable.complete())); + } + + @Test + public void andThenNoInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final boolean[] interrupted = {false}; + + for (int i = 0; i < count; i++) { + Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .andThen(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted[0] = true; + } + } + })) + .subscribe(new Action() { + @Override + public void run() throws Exception { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted[0]); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java index abe412b831..c873d5472d 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableAndThenTest.java @@ -13,10 +13,10 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import io.reactivex.Maybe; import org.junit.Test; +import io.reactivex.*; + public class CompletableAndThenTest { @Test(expected = NullPointerException.class) public void andThenMaybeNull() { diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableCacheTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableCacheTest.java index b34c8dbef8..c32ea2c9ba 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableCacheTest.java @@ -85,50 +85,50 @@ public void error() { public void crossDispose() { PublishSubject<Integer> ps = PublishSubject.create(); - final TestObserver<Void> ts1 = new TestObserver<Void>(); + final TestObserver<Void> to1 = new TestObserver<Void>(); - final TestObserver<Void> ts2 = new TestObserver<Void>() { + final TestObserver<Void> to2 = new TestObserver<Void>() { @Override public void onComplete() { super.onComplete(); - ts1.cancel(); + to1.cancel(); } }; Completable c = ps.ignoreElements().cache(); - c.subscribe(ts2); - c.subscribe(ts1); + c.subscribe(to2); + c.subscribe(to1); ps.onComplete(); - ts1.assertEmpty(); - ts2.assertResult(); + to1.assertEmpty(); + to2.assertResult(); } @Test public void crossDisposeOnError() { PublishSubject<Integer> ps = PublishSubject.create(); - final TestObserver<Void> ts1 = new TestObserver<Void>(); + final TestObserver<Void> to1 = new TestObserver<Void>(); - final TestObserver<Void> ts2 = new TestObserver<Void>() { + final TestObserver<Void> to2 = new TestObserver<Void>() { @Override public void onError(Throwable ex) { super.onError(ex); - ts1.cancel(); + to1.cancel(); } }; Completable c = ps.ignoreElements().cache(); - c.subscribe(ts2); - c.subscribe(ts1); + c.subscribe(to2); + c.subscribe(to1); ps.onError(new TestException()); - ts1.assertEmpty(); - ts2.assertFailure(TestException.class); + to1.assertEmpty(); + to2.assertFailure(TestException.class); } @Test @@ -139,54 +139,54 @@ public void dispose() { assertFalse(ps.hasObservers()); - TestObserver<Void> ts1 = c.test(); + TestObserver<Void> to1 = c.test(); assertTrue(ps.hasObservers()); - ts1.cancel(); + to1.cancel(); assertTrue(ps.hasObservers()); - TestObserver<Void> ts2 = c.test(); + TestObserver<Void> to2 = c.test(); - TestObserver<Void> ts3 = c.test(); - ts3.cancel(); + TestObserver<Void> to3 = c.test(); + to3.cancel(); - TestObserver<Void> ts4 = c.test(true); - ts3.cancel(); + TestObserver<Void> to4 = c.test(true); + to3.cancel(); ps.onComplete(); - ts1.assertEmpty(); + to1.assertEmpty(); - ts2.assertResult(); + to2.assertResult(); - ts3.assertEmpty(); + to3.assertEmpty(); - ts4.assertEmpty(); + to4.assertEmpty(); } @Test public void subscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishSubject<Integer> ps = PublishSubject.create(); final Completable c = ps.ignoreElements().cache(); - final TestObserver<Void> ts1 = new TestObserver<Void>(); + final TestObserver<Void> to1 = new TestObserver<Void>(); - final TestObserver<Void> ts2 = new TestObserver<Void>(); + final TestObserver<Void> to2 = new TestObserver<Void>(); Runnable r1 = new Runnable() { @Override public void run() { - c.subscribe(ts1); + c.subscribe(to1); } }; Runnable r2 = new Runnable() { @Override public void run() { - c.subscribe(ts2); + c.subscribe(to2); } }; @@ -194,32 +194,32 @@ public void run() { ps.onComplete(); - ts1.assertResult(); - ts2.assertResult(); + to1.assertResult(); + to2.assertResult(); } } @Test public void subscribeDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishSubject<Integer> ps = PublishSubject.create(); final Completable c = ps.ignoreElements().cache(); - final TestObserver<Void> ts1 = c.test(); + final TestObserver<Void> to1 = c.test(); - final TestObserver<Void> ts2 = new TestObserver<Void>(); + final TestObserver<Void> to2 = new TestObserver<Void>(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - c.subscribe(ts2); + c.subscribe(to2); } }; @@ -227,8 +227,8 @@ public void run() { ps.onComplete(); - ts1.assertEmpty(); - ts2.assertResult(); + to1.assertEmpty(); + to2.assertResult(); } } @@ -236,31 +236,31 @@ public void run() { public void doubleDispose() { PublishSubject<Integer> ps = PublishSubject.create(); - final TestObserver<Void> ts = new TestObserver<Void>(); + final TestObserver<Void> to = new TestObserver<Void>(); ps.ignoreElements().cache() .subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { - ts.onSubscribe(EmptyDisposable.INSTANCE); + to.onSubscribe(EmptyDisposable.INSTANCE); d.dispose(); d.dispose(); } @Override public void onComplete() { - ts.onComplete(); + to.onComplete(); } @Override public void onError(Throwable e) { - ts.onError(e); + to.onError(e); } }); ps.onComplete(); - ts.assertEmpty(); + to.assertEmpty(); } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableConcatTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableConcatTest.java index efc76109a5..47c21fff85 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableConcatTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.CountDownLatch; import org.junit.Test; import org.reactivestreams.*; @@ -23,7 +24,7 @@ import io.reactivex.*; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.*; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; @@ -34,21 +35,28 @@ public class CompletableConcatTest { @Test public void overflowReported() { - Completable.concat( - Flowable.fromPublisher(new Publisher<Completable>() { - @Override - public void subscribe(Subscriber<? super Completable> s) { - s.onSubscribe(new BooleanSubscription()); - s.onNext(Completable.never()); - s.onNext(Completable.never()); - s.onNext(Completable.never()); - s.onNext(Completable.never()); - s.onComplete(); - } - }), 1 - ) - .test() - .assertFailure(MissingBackpressureException.class); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Completable.concat( + Flowable.fromPublisher(new Publisher<Completable>() { + @Override + public void subscribe(Subscriber<? super Completable> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(Completable.never()); + s.onNext(Completable.never()); + s.onNext(Completable.never()); + s.onNext(Completable.never()); + s.onComplete(); + } + }), 1 + ) + .test() + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertError(errors, 0, MissingBackpressureException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -68,38 +76,38 @@ public void dispose() { @Test public void errorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Void> to = Completable.concat(ps1.map(new Function<Integer, Completable>() { + TestObserver<Void> to = Completable.concat(pp1.map(new Function<Integer, Completable>() { @Override public Completable apply(Integer v) throws Exception { - return ps2.ignoreElements(); + return pp2.ignoreElements(); } })).test(); - ps1.onNext(1); + pp1.onNext(1); final TestException ex = new TestException(); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex); + pp1.onError(ex); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex); + pp2.onError(ex); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -162,10 +170,10 @@ public void arrayFirstCancels() { Completable.concatArray(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); to.cancel(); - s.onComplete(); + observer.onComplete(); } }, Completable.complete()) .subscribe(to); @@ -186,10 +194,10 @@ public void iterableFirstCancels() { Completable.concat(Arrays.asList(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); to.cancel(); - s.onComplete(); + observer.onComplete(); } }, Completable.complete())) .subscribe(to); @@ -202,7 +210,7 @@ public void arrayCancelRace() { Completable[] a = new Completable[1024]; Arrays.fill(a, Completable.complete()); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Completable c = Completable.concatArray(a); @@ -222,7 +230,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -231,7 +239,7 @@ public void iterableCancelRace() { Completable[] a = new Completable[1024]; Arrays.fill(a, Completable.complete()); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Completable c = Completable.concat(Arrays.asList(a)); @@ -251,7 +259,44 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); + } + } + + @Test + public void noInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final boolean[] interrupted = { false }; + + for (int i = 0; i < count; i++) { + Completable c0 = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted[0] = true; + } + } + }); + Completable.concat(Arrays.asList(Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()), + c0) + ) + .subscribe(new Action() { + @Override + public void run() throws Exception { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted[0]); } } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableCreateTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableCreateTest.java index d64484081c..33f50adc5b 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableCreateTest.java @@ -35,70 +35,91 @@ public void nullArgument() { @Test public void basic() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter e) throws Exception { - e.setDisposable(d); + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter e) throws Exception { + e.setDisposable(d); - e.onComplete(); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertResult(); + e.onComplete(); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertResult(); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithCancellable() { - final Disposable d1 = Disposables.empty(); - final Disposable d2 = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d1 = Disposables.empty(); + final Disposable d2 = Disposables.empty(); - Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter e) throws Exception { - e.setDisposable(d1); - e.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - d2.dispose(); - } - }); + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter e) throws Exception { + e.setDisposable(d1); + e.setCancellable(new Cancellable() { + @Override + public void cancel() throws Exception { + d2.dispose(); + } + }); - e.onComplete(); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertResult(); + e.onComplete(); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertResult(); - assertTrue(d1.isDisposed()); - assertTrue(d2.isDisposed()); + assertTrue(d1.isDisposed()); + assertTrue(d2.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter e) throws Exception { - e.setDisposable(d); + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter e) throws Exception { + e.setDisposable(d); - e.onError(new TestException()); - e.onComplete(); - e.onError(new TestException()); - } - }) - .test() - .assertFailure(TestException.class); + e.onError(new TestException()); + e.onComplete(); + e.onError(new TestException("second")); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -297,4 +318,14 @@ public void subscribe(CompletableEmitter e) throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void emitterHasToString() { + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter emitter) throws Exception { + assertTrue(emitter.toString().contains(CompletableCreate.Emitter.class.getSimpleName())); + } + }).test().assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java new file mode 100644 index 0000000000..7148233f9d --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelaySubscriptionTest.java @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subjects.CompletableSubject; + +public class CompletableDelaySubscriptionTest { + + @Test + public void normal() { + final AtomicInteger counter = new AtomicInteger(); + + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertEquals(1, counter.get()); + } + + @Test + public void error() { + final AtomicInteger counter = new AtomicInteger(); + + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + + throw new TestException(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void disposeBeforeTime() { + TestScheduler scheduler = new TestScheduler(); + + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + TestObserver<Void> to = result.test(); + + to.assertEmpty(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + + to.dispose(); + + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + + assertEquals(0, counter.get()); + } + + @Test + public void timestep() { + TestScheduler scheduler = new TestScheduler(); + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + + TestObserver<Void> to = result.test(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + to.assertEmpty(); + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + to.assertResult(); + + assertEquals(1, counter.get()); + } + + @Test + public void timestepError() { + TestScheduler scheduler = new TestScheduler(); + final AtomicInteger counter = new AtomicInteger(); + + Completable result = Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + + throw new TestException(); + } + }) + .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); + + TestObserver<Void> to = result.test(); + + scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + + to.assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void disposeMain() { + CompletableSubject cs = CompletableSubject.create(); + + TestScheduler scheduler = new TestScheduler(); + + TestObserver<Void> to = cs + .delaySubscription(1, TimeUnit.SECONDS, scheduler) + .test(); + + assertFalse(cs.hasObservers()); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + assertTrue(cs.hasObservers()); + + to.dispose(); + + assertFalse(cs.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java index 78393b39ae..be72ce0ad4 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDelayTest.java @@ -13,17 +13,21 @@ package io.reactivex.internal.operators.completable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertNotEquals; + +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import io.reactivex.functions.Consumer; import org.junit.Test; +import io.reactivex.CompletableSource; +import io.reactivex.TestHelper; import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.Schedulers; - -import static org.junit.Assert.assertNotEquals; +import io.reactivex.schedulers.TestScheduler; public class CompletableDelayTest { @@ -58,4 +62,65 @@ public void accept(Throwable throwable) throws Exception { assertNotEquals(Thread.currentThread(), thread.get()); } + @Test + public void disposed() { + TestHelper.checkDisposed(Completable.never().delay(1, TimeUnit.MINUTES)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletable(new Function<Completable, CompletableSource>() { + @Override + public CompletableSource apply(Completable c) throws Exception { + return c.delay(1, TimeUnit.MINUTES); + } + }); + } + + @Test + public void normal() { + Completable.complete() + .delay(1, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + } + + @Test + public void errorNotDelayed() { + TestScheduler scheduler = new TestScheduler(); + + TestObserver<Void> to = Completable.error(new TestException()) + .delay(100, TimeUnit.MILLISECONDS, scheduler, false) + .test(); + + to.assertEmpty(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + to.assertFailure(TestException.class); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + to.assertFailure(TestException.class); + } + + @Test + public void errorDelayed() { + TestScheduler scheduler = new TestScheduler(); + + TestObserver<Void> to = Completable.error(new TestException()) + .delay(100, TimeUnit.MILLISECONDS, scheduler, true) + .test(); + + to.assertEmpty(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + + scheduler.advanceTimeBy(99, TimeUnit.MILLISECONDS); + + to.assertFailure(TestException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDetachTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDetachTest.java new file mode 100644 index 0000000000..850365b2fc --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDetachTest.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.lang.ref.WeakReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.processors.PublishProcessor; + +public class CompletableDetachTest { + + @Test + public void doubleSubscribe() { + + TestHelper.checkDoubleOnSubscribeCompletable(new Function<Completable, CompletableSource>() { + @Override + public CompletableSource apply(Completable m) throws Exception { + return m.onTerminateDetach(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(PublishProcessor.create().ignoreElements().onTerminateDetach()); + } + + @Test + public void onError() { + Completable.error(new TestException()) + .onTerminateDetach() + .test() + .assertFailure(TestException.class); + } + + @Test + public void onComplete() { + Completable.complete() + .onTerminateDetach() + .test() + .assertResult(); + } + + @Test + public void cancelDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Void> to = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(wr.get()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + to.cancel(); + + System.gc(); + Thread.sleep(200); + + to.assertEmpty(); + + assertNull(wr.get()); + } + + @Test + public void completeDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Void> to = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(wr.get()); + observer.onComplete(); + observer.onComplete(); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertResult(); + + assertNull(wr.get()); + } + + @Test + public void errorDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Void> to = new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(wr.get()); + observer.onError(new TestException()); + observer.onError(new IOException()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertFailure(TestException.class); + + assertNull(wr.get()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableDoOnTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableDoOnTest.java index b2082c96d5..5d38ba126f 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableDoOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableDoOnTest.java @@ -85,15 +85,15 @@ public void onSubscribeCrash() { new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(bs); - s.onError(new TestException("Second")); - s.onComplete(); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(bs); + observer.onError(new TestException("Second")); + observer.onComplete(); } } .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException("First"); } }) diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java index 6a590afbbb..42b7025dcf 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromActionTest.java @@ -13,12 +13,15 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import io.reactivex.functions.Action; +import static org.junit.Assert.assertEquals; + import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; -import static org.junit.Assert.assertEquals; +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; public class CompletableFromActionTest { @Test(expected = NullPointerException.class) @@ -97,4 +100,35 @@ public void run() throws Exception { .test() .assertFailure(UnsupportedOperationException.class); } + + @Test + public void fromActionDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + calls.incrementAndGet(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } + + @Test + public void fromActionErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java index 64142918bc..412e3a2d1f 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java @@ -13,12 +13,21 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; -import java.util.concurrent.Callable; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; -import static org.junit.Assert.assertEquals; +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; public class CompletableFromCallableTest { @Test(expected = NullPointerException.class) @@ -100,4 +109,73 @@ public Object call() throws Exception { .test() .assertFailure(UnsupportedOperationException.class); } + + @SuppressWarnings("unchecked") + @Test + public void shouldNotDeliverResultIfSubscriberUnsubscribedBeforeEmission() throws Exception { + Callable<String> func = mock(Callable.class); + + final CountDownLatch funcLatch = new CountDownLatch(1); + final CountDownLatch observerLatch = new CountDownLatch(1); + + when(func.call()).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + observerLatch.countDown(); + + try { + funcLatch.await(); + } catch (InterruptedException e) { + // It's okay, unsubscription causes Thread interruption + + // Restoring interruption status of the Thread + Thread.currentThread().interrupt(); + } + + return "should_not_be_delivered"; + } + }); + + Completable fromCallableObservable = Completable.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + TestObserver<String> outer = new TestObserver<String>(observer); + + fromCallableObservable + .subscribeOn(Schedulers.computation()) + .subscribe(outer); + + // Wait until func will be invoked + observerLatch.await(); + + // Unsubscribing before emission + outer.cancel(); + + // Emitting result + funcLatch.countDown(); + + // func must be invoked + verify(func).call(); + + // Observer must not be notified at all + verify(observer).onSubscribe(any(Disposable.class)); + verifyNoMoreInteractions(observer); + } + + @Test + public void fromActionErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromMaybeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromMaybeTest.java new file mode 100644 index 0000000000..46f62d0b7a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromMaybeTest.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import io.reactivex.Completable; +import io.reactivex.Maybe; +import org.junit.Test; + +public class CompletableFromMaybeTest { + @Test(expected = NullPointerException.class) + public void fromMaybeNull() { + Completable.fromMaybe(null); + } + + @Test + public void fromMaybe() { + Completable.fromMaybe(Maybe.just(1)) + .test() + .assertResult(); + } + + @Test + public void fromMaybeEmpty() { + Completable.fromMaybe(Maybe.<Integer>empty()) + .test() + .assertResult(); + } + + @Test + public void fromMaybeError() { + Completable.fromMaybe(Maybe.error(new UnsupportedOperationException())) + .test() + .assertFailure(UnsupportedOperationException.class); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java index b972e3427e..03db0c5fe0 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromPublisherTest.java @@ -16,6 +16,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.functions.Function; public class CompletableFromPublisherTest { @Test(expected = NullPointerException.class) @@ -48,4 +49,14 @@ public void fromPublisherThrows() { public void dispose() { TestHelper.checkDisposed(Completable.fromPublisher(Flowable.just(1))); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowableToCompletable(new Function<Flowable<Object>, Completable>() { + @Override + public Completable apply(Flowable<Object> f) throws Exception { + return Completable.fromPublisher(f); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java index 48bb226df2..849fd9a81b 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromRunnableTest.java @@ -13,11 +13,14 @@ package io.reactivex.internal.operators.completable; -import io.reactivex.Completable; +import static org.junit.Assert.assertEquals; + import java.util.concurrent.atomic.AtomicInteger; + import org.junit.Test; -import static org.junit.Assert.assertEquals; +import io.reactivex.Completable; +import io.reactivex.exceptions.TestException; public class CompletableFromRunnableTest { @Test(expected = NullPointerException.class) @@ -96,4 +99,35 @@ public void run() { .test() .assertFailure(UnsupportedOperationException.class); } + + @Test + public void fromRunnableDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + calls.incrementAndGet(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } + + @Test + public void fromRunnableErrorsDisposed() { + final AtomicInteger calls = new AtomicInteger(); + Completable.fromRunnable(new Runnable() { + @Override + public void run() { + calls.incrementAndGet(); + throw new TestException(); + } + }) + .test(true) + .assertEmpty(); + + assertEquals(1, calls.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableLiftTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableLiftTest.java index 39e7cd1935..da78bc0bcf 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableLiftTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableLiftTest.java @@ -13,16 +13,19 @@ package io.reactivex.internal.operators.completable; -import static org.junit.Assert.*; +import java.util.List; + import org.junit.Test; import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.plugins.RxJavaPlugins; public class CompletableLiftTest { @Test public void callbackThrows() { + List<Throwable> errors = TestHelper.trackPluginErrors(); try { Completable.complete() .lift(new CompletableOperator() { @@ -32,8 +35,10 @@ public CompletableObserver apply(CompletableObserver o) throws Exception { } }) .test(); - } catch (NullPointerException ex) { - assertTrue(ex.toString(), ex.getCause() instanceof TestException); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); } } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableMaterializeTest.java new file mode 100644 index 0000000000..aec11e5a61 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableMaterializeTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.CompletableSubject; + +public class CompletableMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Completable.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + @SuppressWarnings("unchecked") + public void empty() { + Completable.complete() + .materialize() + .test() + .assertResult(Notification.createOnComplete()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToSingle(new Function<Completable, SingleSource<Notification<Object>>>() { + @Override + public SingleSource<Notification<Object>> apply(Completable v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(CompletableSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeIterableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeIterableTest.java index 934364adf5..fb1b4c709a 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeIterableTest.java @@ -21,14 +21,13 @@ import io.reactivex.exceptions.TestException; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class CompletableMergeIterableTest { @Test public void errorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishSubject<Integer> ps1 = PublishSubject.create(); @@ -52,7 +51,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeTest.java index ba3bdf546f..588dda88f9 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableMergeTest.java @@ -28,7 +28,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class CompletableMergeTest { @Test @@ -47,9 +46,9 @@ public void cancelAfterFirst() { Completable.mergeArray(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); to.cancel(); } }, Completable.complete()) @@ -64,9 +63,9 @@ public void cancelAfterFirstDelayError() { Completable.mergeArrayDelayError(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); to.cancel(); } }, Completable.complete()) @@ -83,10 +82,10 @@ public void onErrorAfterComplete() { Completable.mergeArrayDelayError(Completable.complete(), new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); - co[0] = s; + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + co[0] = observer; } }) .test() @@ -192,7 +191,7 @@ public void innerErrorDelayError() { @Test public void mainErrorInnerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); @@ -223,7 +222,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); Throwable ex = to.errors().get(0); if (ex instanceof CompositeException) { @@ -247,7 +246,7 @@ public void run() { @Test public void mainErrorInnerErrorDelayedRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -277,7 +276,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(CompositeException.class); @@ -409,10 +408,10 @@ public void innerDoubleOnError() { final CompletableObserver[] o = { null }; Completable.mergeDelayError(Flowable.just(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - s.onError(new TestException("First")); - o[0] = s; + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new TestException("First")); + o[0] = observer; } })) .test() @@ -432,13 +431,13 @@ public void innerIsDisposed() { Completable.mergeDelayError(Flowable.just(new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - assertFalse(((Disposable)s).isDisposed()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + assertFalse(((Disposable)observer).isDisposed()); to.dispose(); - assertTrue(((Disposable)s).isDisposed()); + assertTrue(((Disposable)observer).isDisposed()); } })) .subscribe(to); @@ -446,7 +445,7 @@ protected void subscribeActual(CompletableObserver s) { @Test public void mergeArrayInnerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); @@ -472,7 +471,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java new file mode 100644 index 0000000000..1633954fbf --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableResumeNextTest.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; + +public class CompletableResumeNextTest { + + @Test + public void resumeWithError() { + Completable.error(new TestException()) + .onErrorResumeNext(Functions.justFunction(Completable.error(new TestException("second")))) + .test() + .assertFailureAndMessage(TestException.class, "second"); + } + + @Test + public void disposeInMain() { + TestHelper.checkDisposedCompletable(new Function<Completable, CompletableSource>() { + @Override + public CompletableSource apply(Completable c) throws Exception { + return c.onErrorResumeNext(Functions.justFunction(Completable.complete())); + } + }); + } + + @Test + public void disposeInResume() { + TestHelper.checkDisposedCompletable(new Function<Completable, CompletableSource>() { + @Override + public CompletableSource apply(Completable c) throws Exception { + return Completable.error(new TestException()).onErrorResumeNext(Functions.justFunction(c)); + } + }); + } + + @Test + public void disposed() { + TestHelper.checkDisposed( + Completable.error(new TestException()) + .onErrorResumeNext(Functions.justFunction(Completable.never())) + ); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeOnTest.java index fdb64a94c5..cfbc1e6a00 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeOnTest.java @@ -35,13 +35,13 @@ public void normal() { try { TestScheduler scheduler = new TestScheduler(); - TestObserver<Void> ts = Completable.complete() + TestObserver<Void> to = Completable.complete() .subscribeOn(scheduler) .test(); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ts.assertResult(); + to.assertResult(); assertTrue(list.toString(), list.isEmpty()); } finally { diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java index 543d7015c4..4f546e548f 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableSubscribeTest.java @@ -30,7 +30,6 @@ public void subscribeAlreadyCancelled() { assertFalse(pp.hasSubscribers()); } - @Test public void methodTestNoCancel() { PublishSubject<Integer> ps = PublishSubject.create(); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableTakeUntilTest.java new file mode 100644 index 0000000000..abf64f32f0 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableTakeUntilTest.java @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.TestException; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.CompletableSubject; + +public class CompletableTakeUntilTest { + + @Test + public void consumerDisposes() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = cs1.takeUntil(cs2).test(); + + to.assertEmpty(); + + assertTrue(cs1.hasObservers()); + assertTrue(cs2.hasObservers()); + + to.dispose(); + + assertFalse(cs1.hasObservers()); + assertFalse(cs2.hasObservers()); + } + + @Test + public void mainCompletes() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = cs1.takeUntil(cs2).test(); + + to.assertEmpty(); + + assertTrue(cs1.hasObservers()); + assertTrue(cs2.hasObservers()); + + cs1.onComplete(); + + assertFalse(cs1.hasObservers()); + assertFalse(cs2.hasObservers()); + + to.assertResult(); + } + + @Test + public void otherCompletes() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = cs1.takeUntil(cs2).test(); + + to.assertEmpty(); + + assertTrue(cs1.hasObservers()); + assertTrue(cs2.hasObservers()); + + cs2.onComplete(); + + assertFalse(cs1.hasObservers()); + assertFalse(cs2.hasObservers()); + + to.assertResult(); + } + + @Test + public void mainErrors() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = cs1.takeUntil(cs2).test(); + + to.assertEmpty(); + + assertTrue(cs1.hasObservers()); + assertTrue(cs2.hasObservers()); + + cs1.onError(new TestException()); + + assertFalse(cs1.hasObservers()); + assertFalse(cs2.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void otherErrors() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = cs1.takeUntil(cs2).test(); + + to.assertEmpty(); + + assertTrue(cs1.hasObservers()); + assertTrue(cs2.hasObservers()); + + cs2.onError(new TestException()); + + assertFalse(cs1.hasObservers()); + assertFalse(cs2.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void isDisposed() { + CompletableSubject cs1 = CompletableSubject.create(); + CompletableSubject cs2 = CompletableSubject.create(); + + TestHelper.checkDisposed(cs1.takeUntil(cs2)); + } + + @Test + public void mainErrorLate() { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new TestException()); + } + }.takeUntil(Completable.complete()) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void mainCompleteLate() { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + } + }.takeUntil(Completable.complete()) + .test() + .assertResult(); + + assertTrue(errors.isEmpty()); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void otherErrorLate() { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + final AtomicReference<CompletableObserver> ref = new AtomicReference<CompletableObserver>(); + + Completable.complete() + .takeUntil(new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test() + .assertResult(); + + ref.get().onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void otherCompleteLate() { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + final AtomicReference<CompletableObserver> ref = new AtomicReference<CompletableObserver>(); + + Completable.complete() + .takeUntil(new Completable() { + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test() + .assertResult(); + + ref.get().onComplete(); + + assertTrue(errors.isEmpty()); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java index 35ec511859..a0de2b25aa 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimeoutTest.java @@ -13,16 +13,20 @@ package io.reactivex.internal.operators.completable; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.List; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Action; +import io.reactivex.internal.operators.completable.CompletableTimeout.TimeOutObserver; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; @@ -37,7 +41,7 @@ public void timeoutException() throws Exception { .timeout(100, TimeUnit.MILLISECONDS, Schedulers.io()) .test() .awaitDone(5, TimeUnit.SECONDS) - .assertFailure(TimeoutException.class); + .assertFailureAndMessage(TimeoutException.class, timeoutMessage(100, TimeUnit.MILLISECONDS)); } @Test @@ -106,7 +110,7 @@ public void mainError() { @Test public void errorTimeoutRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -133,7 +137,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertTerminated(); @@ -146,4 +150,26 @@ public void run() { } } } + + @Test + public void ambRace() { + TestObserver<Void> to = new TestObserver<Void>(); + to.onSubscribe(Disposables.empty()); + + CompositeDisposable cd = new CompositeDisposable(); + AtomicBoolean once = new AtomicBoolean(); + TimeOutObserver a = new TimeOutObserver(cd, once, to); + + a.onComplete(); + a.onComplete(); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + a.onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimerTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimerTest.java index 1d611a6287..6bd5afd62b 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableTimerTest.java @@ -38,7 +38,7 @@ public void timerInterruptible() throws Exception { try { for (Scheduler s : new Scheduler[] { Schedulers.single(), Schedulers.computation(), Schedulers.newThread(), Schedulers.io(), Schedulers.from(exec) }) { final AtomicBoolean interrupted = new AtomicBoolean(); - TestObserver<Void> ts = Completable.timer(1, TimeUnit.MILLISECONDS, s) + TestObserver<Void> to = Completable.timer(1, TimeUnit.MILLISECONDS, s) .doOnComplete(new Action() { @Override public void run() throws Exception { @@ -53,7 +53,7 @@ public void run() throws Exception { Thread.sleep(500); - ts.cancel(); + to.cancel(); Thread.sleep(500); diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java new file mode 100644 index 0000000000..54661a96db --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableToFlowableTest.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +public class CompletableToFlowableTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToFlowable(new Function<Completable, Publisher<?>>() { + @Override + public Publisher<?> apply(Completable c) throws Exception { + return c.toFlowable(); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableToObservableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableToObservableTest.java new file mode 100644 index 0000000000..b1cda25ef8 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableToObservableTest.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.completable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.fuseable.QueueFuseable; +import io.reactivex.internal.operators.completable.CompletableToObservable.ObserverCompletableObserver; +import io.reactivex.observers.TestObserver; + +public class CompletableToObservableTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToObservable(new Function<Completable, Observable<?>>() { + @Override + public Observable<?> apply(Completable c) throws Exception { + return c.toObservable(); + } + }); + } + + @Test + public void fusion() throws Exception { + TestObserver<Void> to = new TestObserver<Void>(); + + ObserverCompletableObserver co = new ObserverCompletableObserver(to); + + Disposable d = Disposables.empty(); + + co.onSubscribe(d); + + assertEquals(QueueFuseable.NONE, co.requestFusion(QueueFuseable.SYNC)); + + assertEquals(QueueFuseable.ASYNC, co.requestFusion(QueueFuseable.ASYNC)); + + assertEquals(QueueFuseable.ASYNC, co.requestFusion(QueueFuseable.ANY)); + + assertTrue(co.isEmpty()); + + assertNull(co.poll()); + + co.clear(); + + assertFalse(co.isDisposed()); + + co.dispose(); + + assertTrue(d.isDisposed()); + + assertTrue(co.isDisposed()); + + TestHelper.assertNoOffer(co); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableUnsafeTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableUnsafeTest.java index ed2ec31e9f..518e81c3a9 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableUnsafeTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableUnsafeTest.java @@ -14,10 +14,14 @@ package io.reactivex.internal.operators.completable; import static org.junit.Assert.*; + +import java.util.List; + import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.Disposables; +import io.reactivex.plugins.RxJavaPlugins; public class CompletableUnsafeTest { @@ -36,9 +40,9 @@ public void wrapCustomCompletable() { Completable.wrap(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + public void subscribe(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); } }) .test() @@ -49,7 +53,7 @@ public void subscribe(CompletableObserver s) { public void unsafeCreateThrowsNPE() { Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { throw new NullPointerException(); } }).test(); @@ -57,10 +61,11 @@ public void subscribe(CompletableObserver s) { @Test public void unsafeCreateThrowsIAE() { + List<Throwable> errors = TestHelper.trackPluginErrors(); try { Completable.unsafeCreate(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { throw new IllegalArgumentException(); } }).test(); @@ -69,6 +74,10 @@ public void subscribe(CompletableObserver s) { if (!(ex.getCause() instanceof IllegalArgumentException)) { fail(ex.toString() + ": should have thrown NPA(IAE)"); } + + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); } } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java index f5e790731b..5d1a4cfc42 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableUsingTest.java @@ -24,9 +24,9 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class CompletableUsingTest { @@ -347,7 +347,6 @@ public void accept(Object d) throws Exception { .assertFailure(TestException.class); } - @Test public void emptyDisposerCrashes() { Completable.using(new Callable<Object>() { @@ -411,14 +410,14 @@ public Object call() throws Exception { public CompletableSource apply(Object v) throws Exception { return Completable.wrap(new CompletableSource() { @Override - public void subscribe(CompletableObserver s) { + public void subscribe(CompletableObserver observer) { Disposable d1 = Disposables.empty(); - s.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - s.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -440,7 +439,7 @@ public void accept(Object d) throws Exception { @Test public void successDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -477,56 +476,61 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void errorDisposeRace() { - for (int i = 0; i < 500; i++) { - - final PublishSubject<Integer> ps = PublishSubject.create(); - - final TestObserver<Void> to = Completable.using(new Callable<Object>() { - @Override - public Object call() throws Exception { - return 1; - } - }, new Function<Object, CompletableSource>() { - @Override - public CompletableSource apply(Object v) throws Exception { - return ps.ignoreElements(); - } - }, new Consumer<Object>() { - @Override - public void accept(Object d) throws Exception { - } - }, true) - .test(); - - final TestException ex = new TestException(); - - Runnable r1 = new Runnable() { - @Override - public void run() { - to.cancel(); - } - }; - - Runnable r2 = new Runnable() { - @Override - public void run() { - ps.onError(ex); - } - }; - - TestHelper.race(r1, r2, Schedulers.single()); + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final TestObserver<Void> to = Completable.using(new Callable<Object>() { + @Override + public Object call() throws Exception { + return 1; + } + }, new Function<Object, CompletableSource>() { + @Override + public CompletableSource apply(Object v) throws Exception { + return ps.ignoreElements(); + } + }, new Consumer<Object>() { + @Override + public void accept(Object d) throws Exception { + } + }, true) + .test(); + + final TestException ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + TestHelper.race(r1, r2); + } + } finally { + RxJavaPlugins.reset(); } } @Test public void emptyDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -562,7 +566,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstreamTest.java b/src/test/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstreamTest.java index c3ade57ce8..d1d6a0b5d3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstreamTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstreamTest.java @@ -26,8 +26,8 @@ public class AbstractFlowableWithUpstreamTest { @SuppressWarnings("unchecked") @Test public void source() { - Flowable<Integer> o = Flowable.just(1); + Flowable<Integer> f = Flowable.just(1); - assertSame(o, ((HasUpstreamPublisher<Integer>)o.map(Functions.<Integer>identity())).source()); + assertSame(f, ((HasUpstreamPublisher<Integer>)f.map(Functions.<Integer>identity())).source()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java index 92dcd3dd02..a0249db01e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java @@ -43,13 +43,13 @@ public void testSimple() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(timeout = 1000) @@ -68,13 +68,13 @@ public void testSameSourceMultipleIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } @@ -86,7 +86,7 @@ public void testEmpty() { Iterator<Long> it = iter.iterator(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); it.next(); } @@ -165,7 +165,7 @@ public void testFasterSource() { source.onNext(7); source.onComplete(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Ignore("THe target is an enum") diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java index 71c075c2c4..70a43694d4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java @@ -28,7 +28,7 @@ public class BlockingFlowableMostRecentTest { @Test public void testMostRecentNull() { - assertEquals(null, Flowable.<Void>never().blockingMostRecent(null).iterator().next()); + assertNull(Flowable.<Void>never().blockingMostRecent(null).iterator().next()); } @Test @@ -87,12 +87,12 @@ public void testSingleSourceManyIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } @@ -103,7 +103,6 @@ public void constructorshouldbeprivate() { TestHelper.checkUtilityClass(BlockingFlowableMostRecent.class); } - @Test public void empty() { Iterator<Integer> it = Flowable.<Integer>empty() diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java index 356355ab46..e2e2cc4337 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java @@ -241,20 +241,20 @@ public void testNoBufferingOrBlockingOfSequence() throws Throwable { final Flowable<Integer> obs = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> o) { - o.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); task.replace(Schedulers.single().scheduleDirect(new Runnable() { @Override public void run() { try { while (running.get() && !task.isDisposed()) { - o.onNext(count.incrementAndGet()); + subscriber.onNext(count.incrementAndGet()); timeHasPassed.countDown(); } - o.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { - o.onError(e); + subscriber.onError(e); } finally { finished.countDown(); } @@ -308,9 +308,9 @@ public void run() { @Test /* (timeout = 8000) */ public void testSingleSourceManyIterators() throws InterruptedException { - Flowable<Long> o = Flowable.interval(250, TimeUnit.MILLISECONDS); + Flowable<Long> f = Flowable.interval(250, TimeUnit.MILLISECONDS); PublishProcessor<Integer> terminal = PublishProcessor.create(); - Flowable<Long> source = o.takeUntil(terminal); + Flowable<Long> source = f.takeUntil(terminal); Iterable<Long> iter = source.blockingNext(); @@ -318,7 +318,7 @@ public void testSingleSourceManyIterators() throws InterruptedException { BlockingFlowableNext.NextIterator<Long> it = (BlockingFlowableNext.NextIterator<Long>)iter.iterator(); for (long i = 0; i < 10; i++) { - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(j + "th iteration next", Long.valueOf(i), it.next()); } terminal.onNext(1); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java index 87752f2b11..c1363a452b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java @@ -69,10 +69,10 @@ public void testToFutureWithException() { Flowable<String> obs = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onError(new TestException()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onError(new TestException()); } }); @@ -120,6 +120,6 @@ public void testGetWithEmptyFlowable() throws Throwable { public void testGetWithASingleNullItem() throws Exception { Flowable<String> obs = Flowable.just((String)null); Future<String> f = obs.toFuture(); - assertEquals(null, f.get()); + assertNull(f.get()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java index 239aed218d..68490f9257 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java @@ -16,14 +16,18 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.*; import org.reactivestreams.*; import io.reactivex.Flowable; +import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.internal.operators.flowable.BlockingFlowableIterable.BlockingFlowableIterator; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; public class BlockingFlowableToIteratorTest { @@ -33,16 +37,16 @@ public void testToIterator() { Iterator<String> it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("two", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("three", it.next()); - assertEquals(false, it.hasNext()); + assertFalse(it.hasNext()); } @@ -51,19 +55,19 @@ public void testToIteratorWithException() { Flowable<String> obs = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onError(new TestException()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onError(new TestException()); } }); Iterator<String> it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); it.next(); } @@ -185,4 +189,28 @@ protected void subscribeActual(Subscriber<? super Integer> s) { it.next(); } + + @Test(expected = NoSuchElementException.class) + public void disposedIteratorHasNextReturns() { + Iterator<Integer> it = PublishProcessor.<Integer>create() + .blockingIterable().iterator(); + ((Disposable)it).dispose(); + assertFalse(it.hasNext()); + it.next(); + } + + @Test + public void asyncDisposeUnblocks() { + final Iterator<Integer> it = PublishProcessor.<Integer>create() + .blockingIterable().iterator(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + ((Disposable)it).dispose(); + } + }, 1, TimeUnit.SECONDS); + + assertFalse(it.hasNext()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java index 8f184a92ec..409510b23f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -114,8 +113,8 @@ public boolean test(String s) { @Test public void testFollowingFirst() { - Flowable<Integer> o = Flowable.fromArray(1, 3, 5, 6); - Single<Boolean> allOdd = o.all(new Predicate<Integer>() { + Flowable<Integer> f = Flowable.fromArray(1, 3, 5, 6); + Single<Boolean> allOdd = f.all(new Predicate<Integer>() { @Override public boolean test(Integer i) { return i % 2 == 1; @@ -124,6 +123,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Flowable<Integer> source = Flowable.just(1) @@ -146,40 +146,40 @@ public Publisher<Integer> apply(Boolean t1) { @Test @Ignore("No backpressure in Single") public void testBackpressureIfNoneRequestedNoneShouldBeDelivered() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); Flowable.empty().all(new Predicate<Object>() { @Override public boolean test(Object t1) { return false; } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); } @Test public void testBackpressureIfOneRequestedOneShouldBeDelivered() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); Flowable.empty().all(new Predicate<Object>() { @Override public boolean test(Object t) { return false; } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertTerminated(); + to.assertNoErrors(); + to.assertComplete(); - ts.assertValue(true); + to.assertValue(true); } @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); @@ -189,12 +189,12 @@ public boolean test(String v) { throw ex; } }) - .subscribe(ts); + .subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME need to decide about adding the value that probably caused the crash in some way // assertTrue(ex.getCause().getMessage().contains("Boo!")); } @@ -203,7 +203,7 @@ public boolean test(String v) { public void testAllFlowable() { Flowable<String> obs = Flowable.just("one", "two", "six"); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); obs.all(new Predicate<String>() { @Override @@ -212,19 +212,19 @@ public boolean test(String s) { } }) .toFlowable() - .subscribe(observer); + .subscribe(subscriber); - verify(observer).onSubscribe((Subscription)any()); - verify(observer).onNext(true); - verify(observer).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber).onNext(true); + verify(subscriber).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test public void testNotAllFlowable() { Flowable<String> obs = Flowable.just("one", "two", "three", "six"); - Subscriber <Boolean> observer = TestHelper.mockSubscriber(); + Subscriber <Boolean> subscriber = TestHelper.mockSubscriber(); obs.all(new Predicate<String>() { @Override @@ -233,19 +233,19 @@ public boolean test(String s) { } }) .toFlowable() - .subscribe(observer); + .subscribe(subscriber); - verify(observer).onSubscribe((Subscription)any()); - verify(observer).onNext(false); - verify(observer).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber).onNext(false); + verify(subscriber).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test public void testEmptyFlowable() { Flowable<String> obs = Flowable.empty(); - Subscriber <Boolean> observer = TestHelper.mockSubscriber(); + Subscriber <Boolean> subscriber = TestHelper.mockSubscriber(); obs.all(new Predicate<String>() { @Override @@ -254,12 +254,12 @@ public boolean test(String s) { } }) .toFlowable() - .subscribe(observer); + .subscribe(subscriber); - verify(observer).onSubscribe((Subscription)any()); - verify(observer).onNext(true); - verify(observer).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber).onNext(true); + verify(subscriber).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -267,7 +267,7 @@ public void testErrorFlowable() { Throwable error = new Throwable(); Flowable<String> obs = Flowable.error(error); - Subscriber <Boolean> observer = TestHelper.mockSubscriber(); + Subscriber <Boolean> subscriber = TestHelper.mockSubscriber(); obs.all(new Predicate<String>() { @Override @@ -276,17 +276,17 @@ public boolean test(String s) { } }) .toFlowable() - .subscribe(observer); + .subscribe(subscriber); - verify(observer).onSubscribe((Subscription)any()); - verify(observer).onError(error); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber).onError(error); + verifyNoMoreInteractions(subscriber); } @Test public void testFollowingFirstFlowable() { - Flowable<Integer> o = Flowable.fromArray(1, 3, 5, 6); - Flowable<Boolean> allOdd = o.all(new Predicate<Integer>() { + Flowable<Integer> f = Flowable.fromArray(1, 3, 5, 6); + Flowable<Boolean> allOdd = f.all(new Predicate<Integer>() { @Override public boolean test(Integer i) { return i % 2 == 1; @@ -297,6 +297,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamFlowable() { Flowable<Integer> source = Flowable.just(1) @@ -391,13 +392,13 @@ public void predicateThrows() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .all(new Predicate<Integer>() { @@ -422,13 +423,13 @@ public void predicateThrowsObservable() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .all(new Predicate<Integer>() { @@ -451,15 +452,15 @@ public boolean test(Integer v) throws Exception { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.all(Functions.alwaysTrue()); + public Object apply(Flowable<Integer> f) throws Exception { + return f.all(Functions.alwaysTrue()); } }, false, 1, 1, true); TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.all(Functions.alwaysTrue()).toFlowable(); + public Object apply(Flowable<Integer> f) throws Exception { + return f.all(Functions.alwaysTrue()).toFlowable(); } }, false, 1, 1, true); } @@ -468,14 +469,14 @@ public Object apply(Flowable<Integer> o) throws Exception { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Boolean>>() { @Override - public Publisher<Boolean> apply(Flowable<Object> o) throws Exception { - return o.all(Functions.alwaysTrue()).toFlowable(); + public Publisher<Boolean> apply(Flowable<Object> f) throws Exception { + return f.all(Functions.alwaysTrue()).toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, Single<Boolean>>() { @Override - public Single<Boolean> apply(Flowable<Object> o) throws Exception { - return o.all(Functions.alwaysTrue()); + public Single<Boolean> apply(Flowable<Object> f) throws Exception { + return f.all(Functions.alwaysTrue()); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java index f3b7ec2d3e..5b5941fbf4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAmbTest.java @@ -19,8 +19,8 @@ import java.io.IOException; import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -30,6 +30,7 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.util.CrashingMappedIterable; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -94,29 +95,28 @@ public void run() { @Test public void testAmb() { - Flowable<String> Flowable1 = createFlowable(new String[] { + Flowable<String> flowable1 = createFlowable(new String[] { "1", "11", "111", "1111" }, 2000, null); - Flowable<String> Flowable2 = createFlowable(new String[] { + Flowable<String> flowable2 = createFlowable(new String[] { "2", "22", "222", "2222" }, 1000, null); - Flowable<String> Flowable3 = createFlowable(new String[] { + Flowable<String> flowable3 = createFlowable(new String[] { "3", "33", "333", "3333" }, 3000, null); @SuppressWarnings("unchecked") - Flowable<String> o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable<String> f = Flowable.ambArray(flowable1, + flowable2, flowable3); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - o.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + f.subscribe(subscriber); scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("2"); - inOrder.verify(observer, times(1)).onNext("22"); - inOrder.verify(observer, times(1)).onNext("222"); - inOrder.verify(observer, times(1)).onNext("2222"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("2"); + inOrder.verify(subscriber, times(1)).onNext("22"); + inOrder.verify(subscriber, times(1)).onNext("222"); + inOrder.verify(subscriber, times(1)).onNext("2222"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -124,52 +124,50 @@ public void testAmb() { public void testAmb2() { IOException expectedException = new IOException( "fake exception"); - Flowable<String> Flowable1 = createFlowable(new String[] {}, + Flowable<String> flowable1 = createFlowable(new String[] {}, 2000, new IOException("fake exception")); - Flowable<String> Flowable2 = createFlowable(new String[] { + Flowable<String> flowable2 = createFlowable(new String[] { "2", "22", "222", "2222" }, 1000, expectedException); - Flowable<String> Flowable3 = createFlowable(new String[] {}, + Flowable<String> flowable3 = createFlowable(new String[] {}, 3000, new IOException("fake exception")); @SuppressWarnings("unchecked") - Flowable<String> o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable<String> f = Flowable.ambArray(flowable1, + flowable2, flowable3); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - o.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + f.subscribe(subscriber); scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("2"); - inOrder.verify(observer, times(1)).onNext("22"); - inOrder.verify(observer, times(1)).onNext("222"); - inOrder.verify(observer, times(1)).onNext("2222"); - inOrder.verify(observer, times(1)).onError(expectedException); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("2"); + inOrder.verify(subscriber, times(1)).onNext("22"); + inOrder.verify(subscriber, times(1)).onNext("222"); + inOrder.verify(subscriber, times(1)).onNext("2222"); + inOrder.verify(subscriber, times(1)).onError(expectedException); inOrder.verifyNoMoreInteractions(); } @Test public void testAmb3() { - Flowable<String> Flowable1 = createFlowable(new String[] { + Flowable<String> flowable1 = createFlowable(new String[] { "1" }, 2000, null); - Flowable<String> Flowable2 = createFlowable(new String[] {}, + Flowable<String> flowable2 = createFlowable(new String[] {}, 1000, null); - Flowable<String> Flowable3 = createFlowable(new String[] { + Flowable<String> flowable3 = createFlowable(new String[] { "3" }, 3000, null); @SuppressWarnings("unchecked") - Flowable<String> o = Flowable.ambArray(Flowable1, - Flowable2, Flowable3); + Flowable<String> f = Flowable.ambArray(flowable1, + flowable2, flowable3); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - o.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + f.subscribe(subscriber); scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -180,7 +178,7 @@ public void testProducerRequestThroughAmb() { ts.request(3); final AtomicLong requested1 = new AtomicLong(); final AtomicLong requested2 = new AtomicLong(); - Flowable<Integer> o1 = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f1 = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { @@ -200,7 +198,7 @@ public void cancel() { } }); - Flowable<Integer> o2 = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f2 = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { @@ -220,7 +218,7 @@ public void cancel() { } }); - Flowable.ambArray(o1, o2).subscribe(ts); + Flowable.ambArray(f1, f2).subscribe(ts); assertEquals(3, requested1.get()); assertEquals(3, requested2.get()); } @@ -239,7 +237,6 @@ public void testBackpressure() { assertEquals(Flowable.bufferSize() * 2, ts.values().size()); } - @SuppressWarnings("unchecked") @Test public void testSubscriptionOnlyHappensOnce() throws InterruptedException { @@ -252,13 +249,13 @@ public void accept(Subscription s) { }; //this aync stream should emit first - Flowable<Integer> o1 = Flowable.just(1).doOnSubscribe(incrementer) + Flowable<Integer> f1 = Flowable.just(1).doOnSubscribe(incrementer) .delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); //this stream emits second - Flowable<Integer> o2 = Flowable.just(1).doOnSubscribe(incrementer) + Flowable<Integer> f2 = Flowable.just(1).doOnSubscribe(incrementer) .delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - Flowable.ambArray(o1, o2).subscribe(ts); + Flowable.ambArray(f1, f2).subscribe(ts); ts.request(1); ts.awaitTerminalEvent(5, TimeUnit.SECONDS); ts.assertNoErrors(); @@ -269,14 +266,14 @@ public void accept(Subscription s) { @Test public void testSecondaryRequestsPropagatedToChildren() throws InterruptedException { //this aync stream should emit first - Flowable<Integer> o1 = Flowable.fromArray(1, 2, 3) + Flowable<Integer> f1 = Flowable.fromArray(1, 2, 3) .delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); //this stream emits second - Flowable<Integer> o2 = Flowable.fromArray(4, 5, 6) + Flowable<Integer> f2 = Flowable.fromArray(4, 5, 6) .delay(200, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1L); - Flowable.ambArray(o1, o2).subscribe(ts); + Flowable.ambArray(f1, f2).subscribe(ts); // before first emission request 20 more // this request should suffice to emit all ts.request(20); @@ -353,20 +350,20 @@ public void testMultipleUse() { @SuppressWarnings("unchecked") @Test public void ambIterable() { - PublishProcessor<Integer> ps1 = PublishProcessor.create(); - PublishProcessor<Integer> ps2 = PublishProcessor.create(); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); TestSubscriber<Integer> ts = TestSubscriber.create(); - Flowable.amb(Arrays.asList(ps1, ps2)).subscribe(ts); + Flowable.amb(Arrays.asList(pp1, pp2)).subscribe(ts); ts.assertNoValues(); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - assertFalse(ps1.hasSubscribers()); - assertFalse(ps2.hasSubscribers()); + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); ts.assertValue(1); ts.assertNoErrors(); @@ -376,20 +373,20 @@ public void ambIterable() { @SuppressWarnings("unchecked") @Test public void ambIterable2() { - PublishProcessor<Integer> ps1 = PublishProcessor.create(); - PublishProcessor<Integer> ps2 = PublishProcessor.create(); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); TestSubscriber<Integer> ts = TestSubscriber.create(); - Flowable.amb(Arrays.asList(ps1, ps2)).subscribe(ts); + Flowable.amb(Arrays.asList(pp1, pp2)).subscribe(ts); ts.assertNoValues(); - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); - assertFalse(ps1.hasSubscribers()); - assertFalse(ps2.hasSubscribers()); + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); ts.assertValue(2); ts.assertNoErrors(); @@ -567,93 +564,93 @@ public void singleIterable() { @Test public void onNextRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @SuppressWarnings("unchecked") - TestSubscriber<Integer> to = Flowable.ambArray(ps1, ps2).test(); + TestSubscriber<Integer> ts = Flowable.ambArray(pp1, pp2).test(); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onNext(1); + pp1.onNext(1); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onNext(1); + pp2.onNext(1); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertSubscribed().assertNoErrors() + ts.assertSubscribed().assertNoErrors() .assertNotComplete().assertValueCount(1); } } @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @SuppressWarnings("unchecked") - TestSubscriber<Integer> to = Flowable.ambArray(ps1, ps2).test(); + TestSubscriber<Integer> ts = Flowable.ambArray(pp1, pp2).test(); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onComplete(); + pp1.onComplete(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onComplete(); + pp2.onComplete(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertResult(); + ts.assertResult(); } } @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @SuppressWarnings("unchecked") - TestSubscriber<Integer> to = Flowable.ambArray(ps1, ps2).test(); + TestSubscriber<Integer> ts = Flowable.ambArray(pp1, pp2).test(); final Throwable ex = new TestException(); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex); + pp1.onError(ex); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex); + pp2.onError(ex); } }; List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } finally { RxJavaPlugins.reset(); } - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); if (!errors.isEmpty()) { TestHelper.assertUndeliverable(errors, 0, TestException.class); } @@ -717,4 +714,83 @@ public void ambArrayOrder() { Flowable<Integer> error = Flowable.error(new RuntimeException()); Flowable.ambArray(Flowable.just(1), error).test().assertValue(1).assertComplete(); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Flowable.ambArray( + Flowable.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Flowable.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java index 7484568ac2..5c8a9d6f84 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -38,7 +37,7 @@ public class FlowableAnyTest { @Test public void testAnyWithTwoItems() { Flowable<Integer> w = Flowable.just(1, 2); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -47,7 +46,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -57,11 +56,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithTwoItems() { Flowable<Integer> w = Flowable.just(1, 2); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(true); verify(observer, times(1)).onSuccess(false); @@ -71,7 +70,7 @@ public void testIsEmptyWithTwoItems() { @Test public void testAnyWithOneItem() { Flowable<Integer> w = Flowable.just(1); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -80,7 +79,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -90,11 +89,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithOneItem() { Flowable<Integer> w = Flowable.just(1); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(true); verify(observer, times(1)).onSuccess(false); @@ -104,7 +103,7 @@ public void testIsEmptyWithOneItem() { @Test public void testAnyWithEmpty() { Flowable<Integer> w = Flowable.empty(); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -113,7 +112,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -123,11 +122,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithEmpty() { Flowable<Integer> w = Flowable.empty(); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(true); verify(observer, never()).onSuccess(false); @@ -137,7 +136,7 @@ public void testIsEmptyWithEmpty() { @Test public void testAnyWithPredicate1() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; @@ -146,7 +145,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -156,7 +155,7 @@ public boolean test(Integer t1) { @Test public void testExists1() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; @@ -165,7 +164,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -175,7 +174,7 @@ public boolean test(Integer t1) { @Test public void testAnyWithPredicate2() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 1; @@ -184,7 +183,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -195,7 +194,7 @@ public boolean test(Integer t1) { public void testAnyWithEmptyAndPredicate() { // If the source is empty, always output false. Flowable<Integer> w = Flowable.empty(); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t) { return true; @@ -204,7 +203,7 @@ public boolean test(Integer t) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -213,8 +212,8 @@ public boolean test(Integer t) { @Test public void testWithFollowingFirst() { - Flowable<Integer> o = Flowable.fromArray(1, 3, 5, 6); - Single<Boolean> anyEven = o.any(new Predicate<Integer>() { + Flowable<Integer> f = Flowable.fromArray(1, 3, 5, 6); + Single<Boolean> anyEven = f.any(new Predicate<Integer>() { @Override public boolean test(Integer i) { return i % 2 == 0; @@ -223,6 +222,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Flowable<Integer> source = Flowable.just(1).isEmpty() @@ -239,7 +239,7 @@ public Publisher<Integer> apply(Boolean t1) { @Test @Ignore("Single doesn't do backpressure") public void testBackpressureIfNoneRequestedNoneShouldBeDelivered() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); Flowable.just(1).any(new Predicate<Integer>() { @Override @@ -247,32 +247,32 @@ public boolean test(Integer t) { return true; } }) - .subscribe(ts); + .subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); } @Test public void testBackpressureIfOneRequestedOneShouldBeDelivered() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); Flowable.just(1).any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertComplete(); - ts.assertValue(true); + to.assertTerminated(); + to.assertNoErrors(); + to.assertComplete(); + to.assertValue(true); } @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); Flowable.just("Boo!").any(new Predicate<String>() { @@ -280,12 +280,12 @@ public void testPredicateThrowsExceptionAndValueInCauseMessage() { public boolean test(String v) { throw ex; } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME value as last cause? // assertTrue(ex.getCause().getMessage().contains("Boo!")); } @@ -293,7 +293,7 @@ public boolean test(String v) { @Test public void testAnyWithTwoItemsFlowable() { Flowable<Integer> w = Flowable.just(1, 2); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -302,59 +302,59 @@ public boolean test(Integer v) { .toFlowable() ; - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(false); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(false); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testIsEmptyWithTwoItemsFlowable() { Flowable<Integer> w = Flowable.just(1, 2); - Flowable<Boolean> observable = w.isEmpty().toFlowable(); + Flowable<Boolean> flowable = w.isEmpty().toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(true); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(true); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testAnyWithOneItemFlowable() { Flowable<Integer> w = Flowable.just(1); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(false); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(false); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testIsEmptyWithOneItemFlowable() { Flowable<Integer> w = Flowable.just(1); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(true); verify(observer, times(1)).onSuccess(false); @@ -364,123 +364,123 @@ public void testIsEmptyWithOneItemFlowable() { @Test public void testAnyWithEmptyFlowable() { Flowable<Integer> w = Flowable.empty(); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testIsEmptyWithEmptyFlowable() { Flowable<Integer> w = Flowable.empty(); - Flowable<Boolean> observable = w.isEmpty().toFlowable(); + Flowable<Boolean> flowable = w.isEmpty().toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onNext(false); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onNext(false); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testAnyWithPredicate1Flowable() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(false); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(false); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testExists1Flowable() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(false); - verify(observer, times(1)).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext(false); + verify(subscriber, times(1)).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testAnyWithPredicate2Flowable() { Flowable<Integer> w = Flowable.just(1, 2, 3); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 1; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testAnyWithEmptyAndPredicateFlowable() { // If the source is empty, always output false. Flowable<Integer> w = Flowable.empty(); - Flowable<Boolean> observable = w.any(new Predicate<Integer>() { + Flowable<Boolean> flowable = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t) { return true; } }).toFlowable(); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(false); - verify(observer, never()).onNext(true); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(false); + verify(subscriber, never()).onNext(true); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testWithFollowingFirstFlowable() { - Flowable<Integer> o = Flowable.fromArray(1, 3, 5, 6); - Flowable<Boolean> anyEven = o.any(new Predicate<Integer>() { + Flowable<Integer> f = Flowable.fromArray(1, 3, 5, 6); + Flowable<Boolean> anyEven = f.any(new Predicate<Integer>() { @Override public boolean test(Integer i) { return i % 2 == 0; @@ -489,6 +489,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamFlowable() { Flowable<Integer> source = Flowable.just(1).isEmpty() @@ -566,15 +567,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Boolean>>() { @Override - public Publisher<Boolean> apply(Flowable<Object> o) throws Exception { - return o.any(Functions.alwaysTrue()).toFlowable(); + public Publisher<Boolean> apply(Flowable<Object> f) throws Exception { + return f.any(Functions.alwaysTrue()).toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, Single<Boolean>>() { @Override - public Single<Boolean> apply(Flowable<Object> o) throws Exception { - return o.any(Functions.alwaysTrue()); + public Single<Boolean> apply(Flowable<Object> f) throws Exception { + return f.any(Functions.alwaysTrue()); } }); } @@ -585,13 +586,13 @@ public void predicateThrowsSuppressOthers() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new IOException()); - observer.onComplete(); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new IOException()); + subscriber.onComplete(); } } .any(new Predicate<Integer>() { @@ -616,13 +617,13 @@ public void badSourceSingle() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); - observer.onNext(1); - observer.onError(new TestException("Second")); - observer.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException("Second")); + subscriber.onComplete(); } } .any(Functions.alwaysTrue()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java index ad90e3a15c..7dd78955ee 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java @@ -17,12 +17,12 @@ import static org.mockito.Mockito.*; import org.junit.Test; +import org.mockito.Mockito; import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.processors.PublishProcessor; -import io.reactivex.subscribers.DefaultSubscriber; public class FlowableAsObservableTest { @Test @@ -33,17 +33,18 @@ public void testHiding() { assertFalse(dst instanceof PublishProcessor); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - dst.subscribe(o); + dst.subscribe(subscriber); src.onNext(1); src.onComplete(); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testHidingError() { PublishProcessor<Integer> src = PublishProcessor.create(); @@ -52,15 +53,14 @@ public void testHidingError() { assertFalse(dst instanceof PublishProcessor); - @SuppressWarnings("unchecked") - DefaultSubscriber<Object> o = mock(DefaultSubscriber.class); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - dst.subscribe(o); + dst.subscribe(subscriber); src.onError(new TestException()); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(Mockito.<Integer>any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAutoConnectTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAutoConnectTest.java index be92d5ac56..0e70a5070e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAutoConnectTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAutoConnectTest.java @@ -23,10 +23,10 @@ public class FlowableAutoConnectTest { @Test public void autoConnectImmediately() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish().autoConnect(0); + pp.publish().autoConnect(0); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java index cd4aa269d3..636d7bcd28 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBlockingTest.java @@ -62,6 +62,38 @@ public void accept(Integer v) throws Exception { assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } + @Test + public void boundedBlockingSubscribeConsumer() { + final List<Integer> list = new ArrayList<Integer>(); + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + list.add(v); + } + }, 128); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); + } + + @Test + public void boundedBlockingSubscribeConsumerBufferExceed() { + final List<Integer> list = new ArrayList<Integer>(); + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + list.add(v); + } + }, 3); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); + } + @Test public void blockingSubscribeConsumerConsumer() { final List<Object> list = new ArrayList<Object>(); @@ -78,6 +110,38 @@ public void accept(Integer v) throws Exception { assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); } + @Test + public void boundedBlockingSubscribeConsumerConsumer() { + final List<Object> list = new ArrayList<Object>(); + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + list.add(v); + } + }, Functions.emptyConsumer(), 128); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); + } + + @Test + public void boundedBlockingSubscribeConsumerConsumerBufferExceed() { + final List<Object> list = new ArrayList<Object>(); + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + list.add(v); + } + }, Functions.emptyConsumer(), 3); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5), list); + } + @Test public void blockingSubscribeConsumerConsumerError() { final List<Object> list = new ArrayList<Object>(); @@ -98,6 +162,26 @@ public void accept(Object v) throws Exception { assertEquals(Arrays.asList(1, 2, 3, 4, 5, ex), list); } + @Test + public void boundedBlockingSubscribeConsumerConsumerError() { + final List<Object> list = new ArrayList<Object>(); + + TestException ex = new TestException(); + + Consumer<Object> cons = new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + list.add(v); + } + }; + + Flowable.range(1, 5).concatWith(Flowable.<Integer>error(ex)) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(cons, cons, 128); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5, ex), list); + } + @Test public void blockingSubscribeConsumerConsumerAction() { final List<Object> list = new ArrayList<Object>(); @@ -121,6 +205,81 @@ public void run() throws Exception { assertEquals(Arrays.asList(1, 2, 3, 4, 5, 100), list); } + @Test + public void boundedBlockingSubscribeConsumerConsumerAction() { + final List<Object> list = new ArrayList<Object>(); + + Consumer<Object> cons = new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + list.add(v); + } + }; + + Action action = new Action() { + @Override + public void run() throws Exception { + list.add(100); + } + }; + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(cons, cons, action, 128); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 100), list); + } + + @Test + public void boundedBlockingSubscribeConsumerConsumerActionBufferExceed() { + final List<Object> list = new ArrayList<Object>(); + + Consumer<Object> cons = new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + list.add(v); + } + }; + + Action action = new Action() { + @Override + public void run() throws Exception { + list.add(100); + } + }; + + Flowable.range(1, 5) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(cons, cons, action, 3); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 100), list); + } + + @Test + public void boundedBlockingSubscribeConsumerConsumerActionBufferExceedMillionItem() { + final List<Object> list = new ArrayList<Object>(); + + Consumer<Object> cons = new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + list.add(v); + } + }; + + Action action = new Action() { + @Override + public void run() throws Exception { + list.add(1000001); + } + }; + + Flowable.range(1, 1000000) + .subscribeOn(Schedulers.computation()) + .blockingSubscribe(cons, cons, action, 128); + + assertEquals(1000000 + 1, list.size()); + } + @Test public void blockingSubscribeObserver() { final List<Object> list = new ArrayList<Object>(); @@ -130,8 +289,8 @@ public void blockingSubscribeObserver() { .blockingSubscribe(new FlowableSubscriber<Object>() { @Override - public void onSubscribe(Subscription d) { - d.request(Long.MAX_VALUE); + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); } @Override @@ -165,8 +324,8 @@ public void blockingSubscribeObserverError() { .blockingSubscribe(new FlowableSubscriber<Object>() { @Override - public void onSubscribe(Subscription d) { - d.request(Long.MAX_VALUE); + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); } @Override @@ -291,12 +450,12 @@ public void blockingSingleEmpty() { @Test public void onCompleteDelayed() { - TestSubscriber<Object> to = new TestSubscriber<Object>(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(); Flowable.empty().delay(100, TimeUnit.MILLISECONDS) - .blockingSubscribe(to); + .blockingSubscribe(ts); - to.assertResult(); + ts.assertResult(); } @Test @@ -306,46 +465,46 @@ public void utilityClass() { @Test public void disposeUpFront() { - TestSubscriber<Object> to = new TestSubscriber<Object>(); - to.dispose(); - Flowable.just(1).blockingSubscribe(to); + TestSubscriber<Object> ts = new TestSubscriber<Object>(); + ts.dispose(); + Flowable.just(1).blockingSubscribe(ts); - to.assertEmpty(); + ts.assertEmpty(); } @SuppressWarnings("rawtypes") @Test public void delayed() throws Exception { - final TestSubscriber<Object> to = new TestSubscriber<Object>(); + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); final Subscriber[] s = { null }; Schedulers.single().scheduleDirect(new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { - to.dispose(); + ts.dispose(); s[0].onNext(1); } }, 200, TimeUnit.MILLISECONDS); new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - s[0] = observer; + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + s[0] = subscriber; } - }.blockingSubscribe(to); + }.blockingSubscribe(ts); - while (!to.isDisposed()) { + while (!ts.isDisposed()) { Thread.sleep(100); } - to.assertEmpty(); + ts.assertEmpty(); } @Test public void blockinsSubscribeCancelAsync() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final PublishProcessor<Integer> pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java index d2ebe2ca11..d0023d3e38 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java @@ -14,9 +14,9 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -26,24 +26,28 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableBufferBoundarySupplier.BufferBoundarySupplierSubscriber; +import io.reactivex.internal.operators.flowable.FlowableBufferTimed.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.PublishProcessor; +import io.reactivex.processors.*; import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; public class FlowableBufferTest { - private Subscriber<List<String>> observer; + private Subscriber<List<String>> subscriber; private TestScheduler scheduler; private Scheduler.Worker innerScheduler; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); scheduler = new TestScheduler(); innerScheduler = scheduler.createWorker(); } @@ -53,37 +57,37 @@ public void testComplete() { Flowable<String> source = Flowable.empty(); Flowable<List<String>> buffered = source.buffer(3, 3); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - Mockito.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - Mockito.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - Mockito.verify(observer, Mockito.times(1)).onComplete(); + Mockito.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + Mockito.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + Mockito.verify(subscriber, Mockito.times(1)).onComplete(); } @Test public void testSkipAndCountOverlappingBuffers() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onNext("two"); - observer.onNext("three"); - observer.onNext("four"); - observer.onNext("five"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onNext("two"); + subscriber.onNext("three"); + subscriber.onNext("four"); + subscriber.onNext("five"); } }); Flowable<List<String>> buffered = source.buffer(3, 1); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three", "four")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.never()).onComplete(); + InOrder inOrder = Mockito.inOrder(subscriber); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two", "three")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("two", "three", "four")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("three", "four", "five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.never()).onComplete(); } @Test @@ -91,14 +95,14 @@ public void testSkipAndCountGaplessBuffers() { Flowable<String> source = Flowable.just("one", "two", "three", "four", "five"); Flowable<List<String>> buffered = source.buffer(3, 3); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + InOrder inOrder = Mockito.inOrder(subscriber); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two", "three")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test @@ -106,104 +110,104 @@ public void testSkipAndCountBuffersWithGaps() { Flowable<String> source = Flowable.just("one", "two", "three", "four", "five"); Flowable<List<String>> buffered = source.buffer(2, 3); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + InOrder inOrder = Mockito.inOrder(subscriber); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test public void testTimedAndCount() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 90); - push(observer, "three", 110); - push(observer, "four", 190); - push(observer, "five", 210); - complete(observer, 250); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 90); + push(subscriber, "three", 110); + push(subscriber, "four", 190); + push(subscriber, "five", 210); + complete(subscriber, 250); } }); Flowable<List<String>> buffered = source.buffer(100, TimeUnit.MILLISECONDS, scheduler, 2); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); + InOrder inOrder = Mockito.inOrder(subscriber); scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two")); scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("three", "four")); scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test public void testTimed() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 97); - push(observer, "two", 98); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 97); + push(subscriber, "two", 98); /** * Changed from 100. Because scheduling the cut to 100ms happens before this * Flowable even runs due how lift works, pushing at 100ms would execute after the * buffer cut. */ - push(observer, "three", 99); - push(observer, "four", 101); - push(observer, "five", 102); - complete(observer, 150); + push(subscriber, "three", 99); + push(subscriber, "four", 101); + push(subscriber, "five", 102); + complete(subscriber, 150); } }); Flowable<List<String>> buffered = source.buffer(100, TimeUnit.MILLISECONDS, scheduler); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); + InOrder inOrder = Mockito.inOrder(subscriber); scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two", "three")); scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test public void testFlowableBasedOpenerAndCloser() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 500); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 60); + push(subscriber, "three", 110); + push(subscriber, "four", 160); + push(subscriber, "five", 210); + complete(subscriber, 500); } }); Flowable<Object> openings = Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, new Object(), 50); - push(observer, new Object(), 200); - complete(observer, 250); + public void subscribe(Subscriber<Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, new Object(), 50); + push(subscriber, new Object(), 200); + complete(subscriber, 250); } }); @@ -212,39 +216,39 @@ public void subscribe(Subscriber<Object> observer) { public Flowable<Object> apply(Object opening) { return Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, new Object(), 100); - complete(observer, 101); + public void subscribe(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, new Object(), 100); + complete(subscriber, 101); } }); } }; Flowable<List<String>> buffered = source.buffer(openings, closer); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); + InOrder inOrder = Mockito.inOrder(subscriber); scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("two", "three")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test public void testFlowableBasedCloser() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 250); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 60); + push(subscriber, "three", 110); + push(subscriber, "four", 160); + push(subscriber, "five", 210); + complete(subscriber, 250); } }); @@ -253,28 +257,28 @@ public void subscribe(Subscriber<? super String> observer) { public Flowable<Object> call() { return Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, new Object(), 100); - push(observer, new Object(), 200); - push(observer, new Object(), 300); - complete(observer, 301); + public void subscribe(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, new Object(), 100); + push(subscriber, new Object(), 200); + push(subscriber, new Object(), 300); + complete(subscriber, 301); } }); } }; Flowable<List<String>> buffered = source.buffer(closer); - buffered.subscribe(observer); + buffered.subscribe(subscriber); - InOrder inOrder = Mockito.inOrder(observer); + InOrder inOrder = Mockito.inOrder(subscriber); scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.<String>anyList()); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onComplete(); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("one", "two")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("three", "four")); + inOrder.verify(subscriber, Mockito.times(1)).onNext(list("five")); + inOrder.verify(subscriber, Mockito.never()).onNext(Mockito.<String>anyList()); + inOrder.verify(subscriber, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(subscriber, Mockito.times(1)).onComplete(); } @Test @@ -319,20 +323,20 @@ private List<String> list(String... args) { return list; } - private <T> void push(final Subscriber<T> observer, final T value, int delay) { + private <T> void push(final Subscriber<T> subscriber, final T value, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } - private void complete(final Subscriber<?> observer, int delay) { + private void complete(final Subscriber<?> subscriber, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } @@ -341,8 +345,8 @@ public void run() { public void testBufferStopsWhenUnsubscribed1() { Flowable<Integer> source = Flowable.never(); - Subscriber<List<Integer>> o = TestHelper.mockSubscriber(); - TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(o, 0L); + Subscriber<List<Integer>> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(subscriber, 0L); source.buffer(100, 200, TimeUnit.MILLISECONDS, scheduler) .doOnNext(new Consumer<List<Integer>>() { @@ -353,11 +357,11 @@ public void accept(List<Integer> pv) { }) .subscribe(ts); - InOrder inOrder = Mockito.inOrder(o); + InOrder inOrder = Mockito.inOrder(subscriber); scheduler.advanceTimeBy(1001, TimeUnit.MILLISECONDS); - inOrder.verify(o, times(5)).onNext(Arrays.<Integer> asList()); + inOrder.verify(subscriber, times(5)).onNext(Arrays.<Integer> asList()); ts.dispose(); @@ -371,10 +375,10 @@ public void bufferWithBONormal1() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = Mockito.inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = Mockito.inOrder(subscriber); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -382,23 +386,23 @@ public void bufferWithBONormal1() { boundary.onNext(1); - inOrder.verify(o, times(1)).onNext(Arrays.asList(1, 2, 3)); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList(1, 2, 3)); source.onNext(4); source.onNext(5); boundary.onNext(2); - inOrder.verify(o, times(1)).onNext(Arrays.asList(4, 5)); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList(4, 5)); source.onNext(6); boundary.onComplete(); - inOrder.verify(o, times(1)).onNext(Arrays.asList(6)); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList(6)); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -406,18 +410,18 @@ public void bufferWithBOEmptyLastViaBoundary() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = Mockito.inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = Mockito.inOrder(subscriber); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); boundary.onComplete(); - inOrder.verify(o, times(1)).onNext(Arrays.asList()); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList()); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -425,18 +429,18 @@ public void bufferWithBOEmptyLastViaSource() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = Mockito.inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = Mockito.inOrder(subscriber); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); source.onComplete(); - inOrder.verify(o, times(1)).onNext(Arrays.asList()); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList()); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -444,19 +448,19 @@ public void bufferWithBOEmptyLastViaBoth() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = Mockito.inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = Mockito.inOrder(subscriber); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); source.onComplete(); boundary.onComplete(); - inOrder.verify(o, times(1)).onNext(Arrays.asList()); + inOrder.verify(subscriber, times(1)).onNext(Arrays.asList()); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -464,15 +468,15 @@ public void bufferWithBOSourceThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); source.onNext(1); source.onError(new TestException()); - verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -480,30 +484,31 @@ public void bufferWithBOBoundaryThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.buffer(boundary).subscribe(o); + source.buffer(boundary).subscribe(subscriber); source.onNext(1); boundary.onError(new TestException()); - verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } + @Test(timeout = 2000) public void bufferWithSizeTake1() { Flowable<Integer> source = Flowable.just(1).repeat(); Flowable<List<Integer>> result = source.buffer(2).take(1); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 1)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 1)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test(timeout = 2000) @@ -512,48 +517,51 @@ public void bufferWithSizeSkipTake1() { Flowable<List<Integer>> result = source.buffer(2, 3).take(1); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 1)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 1)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeTake1() { Flowable<Long> source = Flowable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); Flowable<List<Long>> result = source.buffer(100, TimeUnit.MILLISECONDS, scheduler).take(1); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - verify(o).onNext(Arrays.asList(0L, 1L)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(0L, 1L)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeSkipTake2() { Flowable<Long> source = Flowable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); Flowable<List<Long>> result = source.buffer(100, 60, TimeUnit.MILLISECONDS, scheduler).take(2); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - inOrder.verify(o).onNext(Arrays.asList(0L, 1L)); - inOrder.verify(o).onNext(Arrays.asList(1L, 2L)); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(0L, 1L)); + inOrder.verify(subscriber).onNext(Arrays.asList(1L, 2L)); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithBoundaryTake2() { Flowable<Long> boundary = Flowable.interval(60, 60, TimeUnit.MILLISECONDS, scheduler); @@ -561,17 +569,17 @@ public void bufferWithBoundaryTake2() { Flowable<List<Long>> result = source.buffer(boundary).take(2); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - inOrder.verify(o).onNext(Arrays.asList(0L)); - inOrder.verify(o).onNext(Arrays.asList(1L)); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(0L)); + inOrder.verify(subscriber).onNext(Arrays.asList(1L)); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -589,8 +597,8 @@ public Flowable<Long> apply(Long t1) { Flowable<List<Long>> result = source.buffer(start, end).take(2); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); result .doOnNext(new Consumer<List<Long>>() { @@ -599,37 +607,38 @@ public void accept(List<Long> pv) { System.out.println(pv); } }) - .subscribe(o); + .subscribe(subscriber); scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - inOrder.verify(o).onNext(Arrays.asList(1L, 2L, 3L)); - inOrder.verify(o).onNext(Arrays.asList(3L, 4L)); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(1L, 2L, 3L)); + inOrder.verify(subscriber).onNext(Arrays.asList(3L, 4L)); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void bufferWithSizeThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); Flowable<List<Integer>> result = source.buffer(2); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); source.onNext(3); source.onError(new TestException()); - inOrder.verify(o).onNext(Arrays.asList(1, 2)); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(1, 2)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(Arrays.asList(3)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(Arrays.asList(3)); + verify(subscriber, never()).onComplete(); } @@ -639,10 +648,10 @@ public void bufferWithTimeThrows() { Flowable<List<Integer>> result = source.buffer(100, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -651,11 +660,11 @@ public void bufferWithTimeThrows() { source.onError(new TestException()); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - inOrder.verify(o).onNext(Arrays.asList(1, 2)); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(1, 2)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(Arrays.asList(3)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(Arrays.asList(3)); + verify(subscriber, never()).onComplete(); } @@ -665,18 +674,19 @@ public void bufferWithTimeAndSize() { Flowable<List<Long>> result = source.buffer(100, TimeUnit.MILLISECONDS, scheduler, 2).take(3); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); scheduler.advanceTimeBy(5, TimeUnit.SECONDS); - inOrder.verify(o).onNext(Arrays.asList(0L, 1L)); - inOrder.verify(o).onNext(Arrays.asList(2L)); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(Arrays.asList(0L, 1L)); + inOrder.verify(subscriber).onNext(Arrays.asList(2L)); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void bufferWithStartEndStartThrows() { PublishProcessor<Integer> start = PublishProcessor.create(); @@ -692,19 +702,20 @@ public Flowable<Integer> apply(Integer t1) { Flowable<List<Integer>> result = source.buffer(start, end); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); start.onNext(1); source.onNext(1); source.onNext(2); start.onError(new TestException()); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndFunctionThrows() { PublishProcessor<Integer> start = PublishProcessor.create(); @@ -720,18 +731,19 @@ public Flowable<Integer> apply(Integer t1) { Flowable<List<Integer>> result = source.buffer(start, end); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); start.onNext(1); source.onNext(1); source.onNext(2); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndThrows() { PublishProcessor<Integer> start = PublishProcessor.create(); @@ -747,17 +759,17 @@ public Flowable<Integer> apply(Integer t1) { Flowable<List<Integer>> result = source.buffer(start, end); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); start.onNext(1); source.onNext(1); source.onNext(2); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } @Test @@ -875,7 +887,6 @@ public void cancel() { assertEquals(Long.MAX_VALUE, requested.get()); } - @Test public void testProducerRequestOverflowThroughBufferWithSize1() { TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(Long.MAX_VALUE >> 1); @@ -985,24 +996,27 @@ public void onNext(List<Integer> t) { // FIXME I'm not sure why this is MAX_VALUE in 1.x because MAX_VALUE/2 is even and thus can't overflow when multiplied by 2 assertEquals(Long.MAX_VALUE - 1, requested.get()); } + @Test(timeout = 3000) public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedException { - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final CountDownLatch cdl = new CountDownLatch(1); ResourceSubscriber<Object> s = new ResourceSubscriber<Object>() { @Override public void onNext(Object t) { - o.onNext(t); + subscriber.onNext(t); } + @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); cdl.countDown(); } + @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); cdl.countDown(); } }; @@ -1011,9 +1025,9 @@ public void onComplete() { cdl.await(); - verify(o).onNext(Arrays.asList(1)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); assertFalse(s.isDisposed()); } @@ -1098,29 +1112,29 @@ public void testPostCompleteBackpressure() { @Test public void timeAndSkipOverlap() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<List<Integer>> ts = TestSubscriber.create(); - ps.buffer(2, 1, TimeUnit.SECONDS, scheduler).subscribe(ts); + pp.buffer(2, 1, TimeUnit.SECONDS, scheduler).subscribe(ts); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(2); + pp.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(3); + pp.onNext(3); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(4); + pp.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onComplete(); + pp.onComplete(); ts.assertValues( Arrays.asList(1, 2), @@ -1138,29 +1152,29 @@ public void timeAndSkipOverlap() { @Test public void timeAndSkipSkip() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<List<Integer>> ts = TestSubscriber.create(); - ps.buffer(2, 3, TimeUnit.SECONDS, scheduler).subscribe(ts); + pp.buffer(2, 3, TimeUnit.SECONDS, scheduler).subscribe(ts); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(2); + pp.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(3); + pp.onNext(3); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(4); + pp.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onComplete(); + pp.onComplete(); ts.assertValues( Arrays.asList(1, 2), @@ -1183,29 +1197,29 @@ public Scheduler apply(Scheduler t) { }); try { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<List<Integer>> ts = TestSubscriber.create(); - ps.buffer(2, 1, TimeUnit.SECONDS).subscribe(ts); + pp.buffer(2, 1, TimeUnit.SECONDS).subscribe(ts); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(2); + pp.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(3); + pp.onNext(3); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(4); + pp.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onComplete(); + pp.onComplete(); ts.assertValues( Arrays.asList(1, 2), @@ -1234,29 +1248,29 @@ public Scheduler apply(Scheduler t) { try { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<List<Integer>> ts = TestSubscriber.create(); - ps.buffer(2, 3, TimeUnit.SECONDS).subscribe(ts); + pp.buffer(2, 3, TimeUnit.SECONDS).subscribe(ts); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(2); + pp.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(3); + pp.onNext(3); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onNext(4); + pp.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ps.onComplete(); + pp.onComplete(); ts.assertValues( Arrays.asList(1, 2), @@ -1847,9 +1861,9 @@ public void bufferTimedOverlapEmpty() { public void bufferTimedExactSupplierCrash() { TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<List<Integer>> to = ps + TestSubscriber<List<Integer>> ts = pp .buffer(1, TimeUnit.MILLISECONDS, scheduler, 1, new Callable<List<Integer>>() { int calls; @Override @@ -1862,13 +1876,13 @@ public List<Integer> call() throws Exception { }, true) .test(); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - ps.onNext(2); + pp.onNext(2); - to + ts .assertFailure(TestException.class, Arrays.asList(1)); } @@ -1877,15 +1891,15 @@ public List<Integer> call() throws Exception { public void bufferTimedExactBoundedError() { TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<List<Integer>> to = ps + TestSubscriber<List<Integer>> ts = pp .buffer(1, TimeUnit.MILLISECONDS, scheduler, 1, Functions.<Integer>createArrayList(16), true) .test(); - ps.onError(new TestException()); + pp.onError(new TestException()); - to + ts .assertFailure(TestException.class); } @@ -1976,22 +1990,22 @@ public void skipBackpressure() { @Test public void withTimeAndSizeCapacityRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestScheduler scheduler = new TestScheduler(); - final PublishProcessor<Object> ps = PublishProcessor.create(); + final PublishProcessor<Object> pp = PublishProcessor.create(); - TestSubscriber<List<Object>> ts = ps.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); + TestSubscriber<List<Object>> ts = pp.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); - ps.onNext(1); - ps.onNext(2); - ps.onNext(3); - ps.onNext(4); + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); Runnable r1 = new Runnable() { @Override public void run() { - ps.onNext(5); + pp.onNext(5); } }; @@ -2004,7 +2018,7 @@ public void run() { TestHelper.race(r1, r2); - ps.onComplete(); + pp.onComplete(); int items = 0; for (List<Object> o : ts.values()) { @@ -2014,4 +2028,760 @@ public void run() { assertEquals("Round: " + i, 5, items); } } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelExact() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable.<Integer>empty() + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(5, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelSkip() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable.<Integer>empty() + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(5, 10, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelOverlap() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable.<Integer>empty() + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(10, 5, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @Test + @SuppressWarnings("unchecked") + public void boundaryOpenCloseDisposedOnComplete() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + assertTrue(source.hasSubscribers()); + assertTrue(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + + openIndicator.onNext(1); + + assertTrue(openIndicator.hasSubscribers()); + assertTrue(closeIndicator.hasSubscribers()); + + source.onComplete(); + + ts.assertResult(Collections.<Integer>emptyList()); + + assertFalse(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + } + + @Test + public void bufferedCanCompleteIfOpenNeverCompletesDropping() { + Flowable.range(1, 50) + .zipWith(Flowable.interval(5, TimeUnit.MILLISECONDS), + new BiFunction<Integer, Long, Integer>() { + @Override + public Integer apply(Integer integer, Long aLong) { + return integer; + } + }) + .buffer(Flowable.interval(0, 200, TimeUnit.MILLISECONDS), + new Function<Long, Publisher<?>>() { + @Override + public Publisher<?> apply(Long a) { + return Flowable.just(a).delay(100, TimeUnit.MILLISECONDS); + } + }) + .test() + .assertSubscribed() + .awaitDone(3, TimeUnit.SECONDS) + .assertComplete(); + } + + @Test + public void bufferedCanCompleteIfOpenNeverCompletesOverlapping() { + Flowable.range(1, 50) + .zipWith(Flowable.interval(5, TimeUnit.MILLISECONDS), + new BiFunction<Integer, Long, Integer>() { + @Override + public Integer apply(Integer integer, Long aLong) { + return integer; + } + }) + .buffer(Flowable.interval(0, 100, TimeUnit.MILLISECONDS), + new Function<Long, Publisher<?>>() { + @Override + public Publisher<?> apply(Long a) { + return Flowable.just(a).delay(200, TimeUnit.MILLISECONDS); + } + }) + .test() + .assertSubscribed() + .awaitDone(3, TimeUnit.SECONDS) + .assertComplete(); + } + + @Test + @SuppressWarnings("unchecked") + public void openClosemainError() { + Flowable.error(new TestException()) + .buffer(Flowable.never(), Functions.justFunction(Flowable.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void openClosebadSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> s) { + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + s.onSubscribe(bs1); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + + s.onSubscribe(bs2); + + assertFalse(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + + s.onError(new IOException()); + s.onComplete(); + s.onNext(1); + s.onError(new TestException()); + } + } + .buffer(Flowable.never(), Functions.justFunction(Flowable.never())) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseOpenCompletes() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + openIndicator.onNext(1); + + assertTrue(closeIndicator.hasSubscribers()); + + openIndicator.onComplete(); + + assertTrue(source.hasSubscribers()); + assertTrue(closeIndicator.hasSubscribers()); + + closeIndicator.onComplete(); + + assertFalse(source.hasSubscribers()); + + ts.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseOpenCompletesNoBuffers() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + openIndicator.onNext(1); + + assertTrue(closeIndicator.hasSubscribers()); + + closeIndicator.onComplete(); + + assertTrue(source.hasSubscribers()); + assertTrue(openIndicator.hasSubscribers()); + + openIndicator.onComplete(); + + assertFalse(source.hasSubscribers()); + + ts.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseTake() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .take(1) + .test(2); + + openIndicator.onNext(1); + closeIndicator.onComplete(); + + assertFalse(source.hasSubscribers()); + assertFalse(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + + ts.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseLimit() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .limit(1) + .test(2); + + openIndicator.onNext(1); + closeIndicator.onComplete(); + + assertFalse(source.hasSubscribers()); + assertFalse(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + + ts.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseEmptyBackpressure() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(0); + + source.onComplete(); + + assertFalse(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + + ts.assertResult(); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseErrorBackpressure() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + PublishProcessor<Integer> openIndicator = PublishProcessor.create(); + + PublishProcessor<Integer> closeIndicator = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(0); + + source.onError(new TestException()); + + assertFalse(openIndicator.hasSubscribers()); + assertFalse(closeIndicator.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseBadOpen() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.never() + .buffer(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> s) { + + assertFalse(((Disposable)s).isDisposed()); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + s.onSubscribe(bs1); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + + s.onSubscribe(bs2); + + assertFalse(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + + s.onError(new IOException()); + + assertTrue(((Disposable)s).isDisposed()); + + s.onComplete(); + s.onNext(1); + s.onError(new TestException()); + } + }, Functions.justFunction(Flowable.never())) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseBadClose() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.never() + .buffer(Flowable.just(1).concatWith(Flowable.<Integer>never()), + Functions.justFunction(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> s) { + + assertFalse(((Disposable)s).isDisposed()); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + s.onSubscribe(bs1); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + + s.onSubscribe(bs2); + + assertFalse(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + + s.onError(new IOException()); + + assertTrue(((Disposable)s).isDisposed()); + + s.onComplete(); + s.onNext(1); + s.onError(new TestException()); + } + })) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void bufferExactBoundaryDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable( + new Function<Flowable<Object>, Flowable<List<Object>>>() { + @Override + public Flowable<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.buffer(Flowable.never()); + } + } + ); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferExactBoundarySecondBufferCrash() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> b = PublishProcessor.create(); + + TestSubscriber<List<Integer>> ts = pp.buffer(b, new Callable<List<Integer>>() { + int calls; + @Override + public List<Integer> call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return new ArrayList<Integer>(); + } + }).test(); + + b.onNext(1); + + ts.assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferExactBoundaryBadSource() { + Flowable<Integer> pp = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onComplete(); + } + }; + + final AtomicReference<Subscriber<? super Integer>> ref = new AtomicReference<Subscriber<? super Integer>>(); + Flowable<Integer> b = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }; + + TestSubscriber<List<Integer>> ts = pp.buffer(b).test(); + + ref.get().onNext(1); + + ts.assertResult(Collections.<Integer>emptyList()); + } + + @Test + public void bufferExactBoundaryCancelUpfront() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> b = PublishProcessor.create(); + + pp.buffer(b).test(0L, true) + .assertEmpty(); + + assertFalse(pp.hasSubscribers()); + assertFalse(b.hasSubscribers()); + } + + @Test + public void bufferExactBoundaryDisposed() { + Flowable<Integer> pp = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + + Disposable d = (Disposable)s; + + assertFalse(d.isDisposed()); + + d.dispose(); + + assertTrue(d.isDisposed()); + } + }; + PublishProcessor<Integer> b = PublishProcessor.create(); + + pp.buffer(b).test(); + } + + @Test + public void bufferBoundaryErrorTwice() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor.createDefault(1) + .buffer(Functions.justCallable(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onError(new TestException("first")); + s.onError(new TestException("second")); + } + })) + .test() + .assertError(TestException.class) + .assertErrorMessage("first") + .assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void bufferBoundarySupplierDisposed() { + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + BufferBoundarySupplierSubscriber<Integer, List<Integer>, Integer> sub = + new BufferBoundarySupplierSubscriber<Integer, List<Integer>, Integer>( + ts, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + Functions.justCallable(Flowable.<Integer>never()) + ); + + BooleanSubscription bs = new BooleanSubscription(); + + sub.onSubscribe(bs); + + assertFalse(sub.isDisposed()); + + sub.dispose(); + + assertTrue(sub.isDisposed()); + + sub.next(); + + assertSame(DisposableHelper.DISPOSED, sub.other.get()); + + sub.cancel(); + sub.cancel(); + + assertTrue(bs.isCancelled()); + } + + @Test + public void bufferBoundarySupplierBufferAlreadyCleared() { + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + BufferBoundarySupplierSubscriber<Integer, List<Integer>, Integer> sub = + new BufferBoundarySupplierSubscriber<Integer, List<Integer>, Integer>( + ts, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + Functions.justCallable(Flowable.<Integer>never()) + ); + + BooleanSubscription bs = new BooleanSubscription(); + + sub.onSubscribe(bs); + + sub.buffer = null; + + sub.next(); + + sub.onNext(1); + + sub.onComplete(); + } + + @Test + public void timedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<List<Object>>>() { + @Override + public Publisher<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.buffer(1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedCancelledUpfront() { + TestScheduler sch = new TestScheduler(); + + TestSubscriber<List<Object>> ts = Flowable.never() + .buffer(1, TimeUnit.MILLISECONDS, sch) + .test(1L, true); + + sch.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + ts.assertEmpty(); + } + + @Test + public void timedInternalState() { + TestScheduler sch = new TestScheduler(); + + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + + BufferExactUnboundedSubscriber<Integer, List<Integer>> sub = new BufferExactUnboundedSubscriber<Integer, List<Integer>>( + ts, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), 1, TimeUnit.SECONDS, sch); + + sub.onSubscribe(new BooleanSubscription()); + + assertFalse(sub.isDisposed()); + + sub.onError(new TestException()); + sub.onNext(1); + sub.onComplete(); + + sub.run(); + + sub.dispose(); + + assertTrue(sub.isDisposed()); + + sub.buffer = new ArrayList<Integer>(); + sub.enter(); + sub.onComplete(); + } + + @Test + public void timedSkipDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<List<Object>>>() { + @Override + public Publisher<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.buffer(2, 1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedSizedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<List<Object>>>() { + @Override + public Publisher<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.buffer(2, TimeUnit.SECONDS, 10); + } + }); + } + + @Test + public void timedSkipInternalState() { + TestScheduler sch = new TestScheduler(); + + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + + BufferSkipBoundedSubscriber<Integer, List<Integer>> sub = new BufferSkipBoundedSubscriber<Integer, List<Integer>>( + ts, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), 1, 1, TimeUnit.SECONDS, sch.createWorker()); + + sub.onSubscribe(new BooleanSubscription()); + + sub.enter(); + sub.onComplete(); + + sub.cancel(); + + sub.run(); + } + + @Test + public void timedSkipCancelWhenSecondBuffer() { + TestScheduler sch = new TestScheduler(); + + final TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + + BufferSkipBoundedSubscriber<Integer, List<Integer>> sub = new BufferSkipBoundedSubscriber<Integer, List<Integer>>( + ts, new Callable<List<Integer>>() { + int calls; + @Override + public List<Integer> call() throws Exception { + if (++calls == 2) { + ts.cancel(); + } + return new ArrayList<Integer>(); + } + }, 1, 1, TimeUnit.SECONDS, sch.createWorker()); + + sub.onSubscribe(new BooleanSubscription()); + + sub.run(); + + assertTrue(ts.isCancelled()); + } + + @Test + public void timedSizeBufferAlreadyCleared() { + TestScheduler sch = new TestScheduler(); + + TestSubscriber<List<Integer>> ts = new TestSubscriber<List<Integer>>(); + + BufferExactBoundedSubscriber<Integer, List<Integer>> sub = + new BufferExactBoundedSubscriber<Integer, List<Integer>>( + ts, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + 1, TimeUnit.SECONDS, 1, false, sch.createWorker()) + ; + + BooleanSubscription bs = new BooleanSubscription(); + + sub.onSubscribe(bs); + + sub.producerIndex++; + + sub.run(); + + assertFalse(sub.isDisposed()); + + sub.enter(); + sub.onComplete(); + + assertTrue(sub.isDisposed()); + + sub.run(); + } + + @Test + @SuppressWarnings("unchecked") + public void bufferExactFailingSupplier() { + Flowable.empty() + .buffer(1, TimeUnit.SECONDS, Schedulers.computation(), 10, new Callable<List<Object>>() { + @Override + public List<Object> call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .awaitDone(1, TimeUnit.SECONDS) + .assertFailure(TestException.class) + ; + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java index 526b8bcd77..4208b18dec 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCacheTest.java @@ -54,6 +54,7 @@ public void testColdReplayNoBackpressure() { assertEquals((Integer)i, onNextEvents.get(i)); } } + @Test public void testColdReplayBackpressure() { FlowableCache<Integer> source = new FlowableCache<Integer>(Flowable.range(0, 1000), 16); @@ -84,19 +85,19 @@ public void testColdReplayBackpressure() { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); System.out.println("published observable being executed"); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } @@ -106,7 +107,7 @@ public void run() { final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -116,7 +117,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); @@ -134,11 +135,11 @@ public void accept(String v) { @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); - Flowable<Integer> o = Flowable.just(1).doOnCancel(unsubscribe).cache(); - o.subscribe(); - o.subscribe(); - o.subscribe(); - verify(unsubscribe, times(1)).run(); + Flowable<Integer> f = Flowable.just(1).doOnCancel(unsubscribe).cache(); + f.subscribe(); + f.subscribe(); + f.subscribe(); + verify(unsubscribe, never()).run(); } @Test @@ -178,6 +179,7 @@ public void testAsync() { assertEquals(10000, ts2.values().size()); } } + @Test public void testAsyncComeAndGo() { Flowable<Long> source = Flowable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -244,7 +246,6 @@ public void testValuesAndThenError() { .concatWith(Flowable.<Integer>error(new TestException())) .cache(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.subscribe(ts); @@ -305,29 +306,29 @@ public void dispose() { @Test public void disposeOnArrival2() { - Flowable<Integer> o = PublishProcessor.<Integer>create().cache(); + Flowable<Integer> f = PublishProcessor.<Integer>create().cache(); - o.test(); + f.test(); - o.test(0L, true) + f.test(0L, true) .assertEmpty(); } @Test public void subscribeEmitRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.<Integer>create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.<Integer>create(); - final Flowable<Integer> cache = ps.cache(); + final Flowable<Integer> cache = pp.cache(); cache.test(); - final TestSubscriber<Integer> to = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { - cache.subscribe(to); + cache.subscribe(ts); } }; @@ -335,15 +336,15 @@ public void run() { @Override public void run() { for (int j = 0; j < 500; j++) { - ps.onNext(j); + pp.onNext(j); } - ps.onComplete(); + pp.onComplete(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertSubscribed().assertValueCount(500).assertComplete().assertNoErrors(); } @@ -351,22 +352,22 @@ public void run() { @Test public void observers() { - PublishProcessor<Integer> ps = PublishProcessor.create(); - FlowableCache<Integer> cache = (FlowableCache<Integer>)Flowable.range(1, 5).concatWith(ps).cache(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + FlowableCache<Integer> cache = (FlowableCache<Integer>)Flowable.range(1, 5).concatWith(pp).cache(); assertFalse(cache.hasSubscribers()); assertEquals(0, cache.cachedEventCount()); - TestSubscriber<Integer> to = cache.test(); + TestSubscriber<Integer> ts = cache.test(); assertTrue(cache.hasSubscribers()); assertEquals(5, cache.cachedEventCount()); - ps.onComplete(); + pp.onComplete(); - to.assertResult(1, 2, 3, 4, 5); + ts.assertResult(1, 2, 3, 4, 5); } @Test @@ -419,4 +420,124 @@ public void error() { .test(0L) .assertFailure(TestException.class); } + + @Test + public void cancelledUpFrontConnectAnyway() { + final AtomicInteger call = new AtomicInteger(); + Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return call.incrementAndGet(); + } + }) + .cache() + .test(1L, true) + .assertNoValues(); + + assertEquals(1, call.get()); + } + + @Test + public void cancelledUpFront() { + final AtomicInteger call = new AtomicInteger(); + Flowable<Object> f = Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return call.incrementAndGet(); + } + }).concatWith(Flowable.never()) + .cache(); + + f.test().assertValuesOnly(1); + + f.test(1L, true) + .assertEmpty(); + + assertEquals(1, call.get()); + } + + @Test + public void subscribeSubscribeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final Flowable<Integer> cache = Flowable.range(1, 500).cache(); + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cache.subscribe(ts1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cache.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2); + + ts1 + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertComplete() + .assertNoErrors(); + + ts2 + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(500) + .assertComplete() + .assertNoErrors(); + } + } + + @Test + public void subscribeCompleteRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.<Integer>create(); + + final Flowable<Integer> cache = pp.cache(); + + cache.test(); + + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cache.subscribe(ts); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + pp.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + } + } + + @Test + public void backpressure() { + Flowable.range(1, 5) + .cache() + .test(0) + .assertEmpty() + .requestMore(2) + .assertValuesOnly(1, 2) + .requestMore(3) + .assertResult(1, 2, 3, 4, 5); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCastTest.java index 02928041fb..4506f67afe 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCastTest.java @@ -27,46 +27,44 @@ public class FlowableCastTest { @Test public void testCast() { Flowable<?> source = Flowable.just(1, 2); - Flowable<Integer> observable = source.cast(Integer.class); + Flowable<Integer> flowable = source.cast(Integer.class); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(1); - verify(observer, never()).onError( - any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testCastWithWrongType() { Flowable<?> source = Flowable.just(1, 2); - Flowable<Boolean> observable = source.cast(Boolean.class); + Flowable<Boolean> flowable = source.cast(Boolean.class); - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onError( - any(ClassCastException.class)); + verify(subscriber, times(1)).onError(any(ClassCastException.class)); } @Test public void castCrashUnsubscribes() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<String> ts = TestSubscriber.create(); - ps.cast(String.class).subscribe(ts); + pp.cast(String.class).subscribe(ts); - Assert.assertTrue("Not subscribed?", ps.hasSubscribers()); + Assert.assertTrue("Not subscribed?", pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - Assert.assertFalse("Subscribed?", ps.hasSubscribers()); + Assert.assertFalse("Subscribed?", pp.hasSubscribers()); ts.assertError(ClassCastException.class); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java index 61c87cbf4f..a96b8c8193 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.*; @@ -178,16 +177,16 @@ public void testCombineLatest2Types() { BiFunction<String, Integer, String> combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); /* define an Observer to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> w = Flowable.combineLatest(Flowable.just("one", "two"), Flowable.just(2, 3, 4), combineLatestFunction); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("two2"); - verify(observer, times(1)).onNext("two3"); - verify(observer, times(1)).onNext("two4"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("two2"); + verify(subscriber, times(1)).onNext("two3"); + verify(subscriber, times(1)).onNext("two4"); } @Test @@ -195,14 +194,14 @@ public void testCombineLatest3TypesA() { Function3<String, Integer, int[], String> combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); /* define an Observer to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> w = Flowable.combineLatest(Flowable.just("one", "two"), Flowable.just(2), Flowable.just(new int[] { 4, 5, 6 }), combineLatestFunction); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("two2[4, 5, 6]"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("two2[4, 5, 6]"); } @Test @@ -210,15 +209,15 @@ public void testCombineLatest3TypesB() { Function3<String, Integer, int[], String> combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); /* define an Observer to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> w = Flowable.combineLatest(Flowable.just("one"), Flowable.just(2), Flowable.just(new int[] { 4, 5, 6 }, new int[] { 7, 8 }), combineLatestFunction); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one2[4, 5, 6]"); - verify(observer, times(1)).onNext("one2[7, 8]"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one2[4, 5, 6]"); + verify(subscriber, times(1)).onNext("one2[7, 8]"); } private Function3<String, String, String, String> getConcat3StringsCombineLatestFunction() { @@ -285,33 +284,33 @@ public void combineSimple() { Flowable<Integer> source = Flowable.combineLatest(a, b, or); - Subscriber<Object> observer = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.subscribe(observer); + source.subscribe(subscriber); a.onNext(1); - inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(subscriber, never()).onNext(any()); a.onNext(2); - inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(subscriber, never()).onNext(any()); b.onNext(0x10); - inOrder.verify(observer, times(1)).onNext(0x12); + inOrder.verify(subscriber, times(1)).onNext(0x12); b.onNext(0x20); - inOrder.verify(observer, times(1)).onNext(0x22); + inOrder.verify(subscriber, times(1)).onNext(0x22); b.onComplete(); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, never()).onComplete(); a.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); a.onNext(3); b.onNext(0x30); @@ -319,7 +318,7 @@ public void combineSimple() { b.onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -329,44 +328,44 @@ public void combineMultipleObservers() { Flowable<Integer> source = Flowable.combineLatest(a, b, or); - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); - Subscriber<Object> observer2 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); - InOrder inOrder1 = inOrder(observer1); - InOrder inOrder2 = inOrder(observer2); + InOrder inOrder1 = inOrder(subscriber1); + InOrder inOrder2 = inOrder(subscriber2); - source.subscribe(observer1); - source.subscribe(observer2); + source.subscribe(subscriber1); + source.subscribe(subscriber2); a.onNext(1); - inOrder1.verify(observer1, never()).onNext(any()); - inOrder2.verify(observer2, never()).onNext(any()); + inOrder1.verify(subscriber1, never()).onNext(any()); + inOrder2.verify(subscriber2, never()).onNext(any()); a.onNext(2); - inOrder1.verify(observer1, never()).onNext(any()); - inOrder2.verify(observer2, never()).onNext(any()); + inOrder1.verify(subscriber1, never()).onNext(any()); + inOrder2.verify(subscriber2, never()).onNext(any()); b.onNext(0x10); - inOrder1.verify(observer1, times(1)).onNext(0x12); - inOrder2.verify(observer2, times(1)).onNext(0x12); + inOrder1.verify(subscriber1, times(1)).onNext(0x12); + inOrder2.verify(subscriber2, times(1)).onNext(0x12); b.onNext(0x20); - inOrder1.verify(observer1, times(1)).onNext(0x22); - inOrder2.verify(observer2, times(1)).onNext(0x22); + inOrder1.verify(subscriber1, times(1)).onNext(0x22); + inOrder2.verify(subscriber2, times(1)).onNext(0x22); b.onComplete(); - inOrder1.verify(observer1, never()).onComplete(); - inOrder2.verify(observer2, never()).onComplete(); + inOrder1.verify(subscriber1, never()).onComplete(); + inOrder2.verify(subscriber2, never()).onComplete(); a.onComplete(); - inOrder1.verify(observer1, times(1)).onComplete(); - inOrder2.verify(observer2, times(1)).onComplete(); + inOrder1.verify(subscriber1, times(1)).onComplete(); + inOrder2.verify(subscriber2, times(1)).onComplete(); a.onNext(3); b.onNext(0x30); @@ -375,8 +374,8 @@ public void combineMultipleObservers() { inOrder1.verifyNoMoreInteractions(); inOrder2.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); - verify(observer2, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); + verify(subscriber2, never()).onError(any(Throwable.class)); } @Test @@ -386,19 +385,19 @@ public void testFirstNeverProduces() { Flowable<Integer> source = Flowable.combineLatest(a, b, or); - Subscriber<Object> observer = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.subscribe(observer); + source.subscribe(subscriber); b.onNext(0x10); b.onNext(0x20); a.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); - verify(observer, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -408,10 +407,10 @@ public void testSecondNeverProduces() { Flowable<Integer> source = Flowable.combineLatest(a, b, or); - Subscriber<Object> observer = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.subscribe(observer); + source.subscribe(subscriber); a.onNext(0x1); a.onNext(0x2); @@ -419,9 +418,9 @@ public void testSecondNeverProduces() { b.onComplete(); a.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); - verify(observer, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } public void test0Sources() { @@ -449,13 +448,13 @@ public List<Object> apply(Object[] args) { Flowable<List<Object>> result = Flowable.combineLatest(sources, func); - Subscriber<List<Object>> o = TestHelper.mockSubscriber(); + Subscriber<List<Object>> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(values); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(values); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } @@ -480,7 +479,7 @@ public List<Object> apply(Object[] args) { Flowable<List<Object>> result = Flowable.combineLatest(sources, func); - final Subscriber<List<Object>> o = TestHelper.mockSubscriber(); + final Subscriber<List<Object>> subscriber = TestHelper.mockSubscriber(); final CountDownLatch cdl = new CountDownLatch(1); @@ -488,18 +487,18 @@ public List<Object> apply(Object[] args) { @Override public void onNext(List<Object> t) { - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); cdl.countDown(); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); cdl.countDown(); } }; @@ -508,9 +507,9 @@ public void onComplete() { cdl.await(); - verify(o).onNext(values); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(values); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } @@ -527,13 +526,13 @@ public List<Integer> apply(Integer t1, Integer t2) { } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -550,13 +549,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3) { } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -574,13 +573,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4) { } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -599,13 +598,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4, Integ } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4, 5)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4, 5)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -625,13 +624,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4, Integ } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4, 5, 6)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4, 5, 6)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -652,13 +651,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4, Integ } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -680,13 +679,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4, Integ } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -709,13 +708,13 @@ public List<Integer> apply(Integer t1, Integer t2, Integer t3, Integer t4, Integ } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -730,13 +729,13 @@ public Object apply(Object[] args) { }); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onComplete(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -751,11 +750,11 @@ public void testBackpressureLoop() { public void testBackpressure() { BiFunction<String, Integer, String> combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); - int NUM = Flowable.bufferSize() * 4; + int num = Flowable.bufferSize() * 4; TestSubscriber<String> ts = new TestSubscriber<String>(); Flowable.combineLatest( Flowable.just("one", "two"), - Flowable.range(2, NUM), + Flowable.range(2, num), combineLatestFunction ) .observeOn(Schedulers.computation()) @@ -767,7 +766,7 @@ public void testBackpressure() { assertEquals("two2", events.get(0)); assertEquals("two3", events.get(1)); assertEquals("two4", events.get(2)); - assertEquals(NUM, events.size()); + assertEquals(num, events.size()); } @Test @@ -807,15 +806,15 @@ public Long apply(Long t1, Integer t2) { public void testCombineLatestRequestOverflow() throws InterruptedException { @SuppressWarnings("unchecked") List<Flowable<Integer>> sources = Arrays.asList(Flowable.fromArray(1, 2, 3, 4), - Flowable.fromArray(5,6,7,8)); - Flowable<Integer> o = Flowable.combineLatest(sources,new Function<Object[], Integer>() { + Flowable.fromArray(5, 6, 7, 8)); + Flowable<Integer> f = Flowable.combineLatest(sources, new Function<Object[], Integer>() { @Override public Integer apply(Object[] args) { return (Integer) args[0]; }}); //should get at least 4 final CountDownLatch latch = new CountDownLatch(4); - o.subscribeOn(Schedulers.computation()).subscribe(new DefaultSubscriber<Integer>() { + f.subscribeOn(Schedulers.computation()).subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { @@ -1206,6 +1205,7 @@ public Object apply(Object[] a) throws Exception { .test() .assertFailure(TestException.class, "[1, 2]"); } + @SuppressWarnings("unchecked") @Test public void combineLatestEmpty() { @@ -1242,14 +1242,14 @@ public Object apply(Object a, Object b) throws Exception { @Test public void cancelWhileSubscribing() { - final TestSubscriber<Object> to = new TestSubscriber<Object>(); + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); Flowable.combineLatest( Flowable.just(1) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { - to.cancel(); + ts.cancel(); } }), Flowable.never(), @@ -1259,18 +1259,18 @@ public Object apply(Object a, Object b) throws Exception { return a; } }) - .subscribe(to); + .subscribe(ts); } @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.combineLatest(ps1, ps2, new BiFunction<Integer, Integer, Integer>() { + TestSubscriber<Integer> ts = Flowable.combineLatest(pp1, pp2, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a; @@ -1283,30 +1283,30 @@ public Integer apply(Integer a, Integer b) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex1); + pp1.onError(ex1); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex2); + pp2.onError(ex2); } }; TestHelper.race(r1, r2); - if (to.errorCount() != 0) { - if (to.errors().get(0) instanceof CompositeException) { - to.assertSubscribed() + if (ts.errorCount() != 0) { + if (ts.errors().get(0) instanceof CompositeException) { + ts.assertSubscribed() .assertNotComplete() .assertNoValues(); - for (Throwable e : TestHelper.errorList(to)) { + for (Throwable e : TestHelper.errorList(ts)) { assertTrue(e.toString(), e instanceof TestException); } } else { - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); } } @@ -1382,7 +1382,7 @@ public void dontSubscribeIfDone() { Flowable.error(new TestException()) .doOnSubscribe(new Consumer<Subscription>() { @Override - public void accept(Subscription d) throws Exception { + public void accept(Subscription s) throws Exception { count[0]++; } }), @@ -1415,7 +1415,7 @@ public void dontSubscribeIfDone2() { Flowable.error(new TestException()) .doOnSubscribe(new Consumer<Subscription>() { @Override - public void accept(Subscription d) throws Exception { + public void accept(Subscription s) throws Exception { count[0]++; } }) @@ -1569,4 +1569,23 @@ public Integer apply(Integer t1, Integer t2) throws Exception { .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value"); } + + @Test + @SuppressWarnings("unchecked") + public void syncFirstErrorsAfterItemDelayError() { + Flowable.combineLatestDelayError(Arrays.asList( + Flowable.just(21).concatWith(Flowable.<Integer>error(new TestException())), + Flowable.just(21).delay(100, TimeUnit.MILLISECONDS) + ), + new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return (Integer)a[0] + (Integer)a[1]; + } + } + ) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class, 42); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java index ab307df66f..7d343c1f3d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatDelayErrorTest.java @@ -216,7 +216,6 @@ static <T> Flowable<T> withError(Flowable<T> source) { return source.concatWith(Flowable.<T>error(new TestException())); } - @Test public void concatDelayErrorFlowable() { TestSubscriber<Integer> ts = TestSubscriber.create(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java index d4e0937350..11f12fb4a3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerTest.java @@ -588,11 +588,11 @@ public Flowable<Integer> apply(Integer t) { @Test public void testReentrantWork() { - final PublishProcessor<Integer> subject = PublishProcessor.create(); + final PublishProcessor<Integer> processor = PublishProcessor.create(); final AtomicBoolean once = new AtomicBoolean(); - subject.concatMapEager(new Function<Integer, Flowable<Integer>>() { + processor.concatMapEager(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { return Flowable.just(t); @@ -602,13 +602,13 @@ public Flowable<Integer> apply(Integer t) { @Override public void accept(Integer t) { if (once.compareAndSet(false, true)) { - subject.onNext(2); + processor.onNext(2); } } }) .subscribe(ts); - subject.onNext(1); + processor.onNext(1); ts.assertNoErrors(); ts.assertNotComplete(); @@ -655,7 +655,6 @@ public Flowable<Integer> apply(Integer t) { ts.assertValue(null); } - @Test public void testMaxConcurrent5() { final List<Long> requests = new ArrayList<Long>(); @@ -856,49 +855,49 @@ public Flowable<Integer> apply(Integer v) throws Exception { @Test public void innerOuterRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestSubscriber<Integer> to = ps1.concatMapEager(new Function<Integer, Flowable<Integer>>() { + TestSubscriber<Integer> ts = pp1.concatMapEager(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { - return ps2; + return pp2; } }).test(); final TestException ex1 = new TestException(); final TestException ex2 = new TestException(); - ps1.onNext(1); + pp1.onNext(1); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex1); + pp1.onError(ex1); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex2); + pp2.onError(ex2); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertSubscribed().assertNoValues().assertNotComplete(); + ts.assertSubscribed().assertNoValues().assertNotComplete(); - Throwable ex = to.errors().get(0); + Throwable ex = ts.errors().get(0); if (ex instanceof CompositeException) { - List<Throwable> es = TestHelper.errorList(to); + List<Throwable> es = TestHelper.errorList(ts); TestHelper.assertError(es, 0, TestException.class); TestHelper.assertError(es, 1, TestException.class); } else { - to.assertError(TestException.class); + ts.assertError(TestException.class); if (!errors.isEmpty()) { TestHelper.assertUndeliverable(errors, 0, TestException.class); } @@ -943,7 +942,7 @@ public void innerErrorAfterPoll() { final UnicastProcessor<Integer> us = UnicastProcessor.create(); us.onNext(1); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -958,18 +957,18 @@ public Flowable<Integer> apply(Integer v) throws Exception { return us; } }, 1, 128) - .subscribe(to); + .subscribe(ts); - to + ts .assertFailure(TestException.class, 1); } @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps1.concatMapEager(new Function<Integer, Flowable<Integer>>() { + final TestSubscriber<Integer> ts = pp1.concatMapEager(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return Flowable.never(); @@ -979,37 +978,37 @@ public Flowable<Integer> apply(Integer v) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onNext(1); + pp1.onNext(1); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertEmpty(); + ts.assertEmpty(); } } @Test public void mapperCancels() { - final TestSubscriber<Integer> to = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.just(1).hide() .concatMapEager(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { - to.cancel(); + ts.cancel(); return Flowable.never(); } }, 1, 128) - .subscribe(to); + .subscribe(ts); - to.assertEmpty(); + ts.assertEmpty(); } @Test @@ -1051,8 +1050,8 @@ public Flowable<Integer> apply(Integer v) throws Exception { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.concatMapEager(new Function<Object, Flowable<Object>>() { + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.concatMapEager(new Function<Object, Flowable<Object>>() { @Override public Flowable<Object> apply(Object v) throws Exception { return Flowable.just(v); @@ -1136,7 +1135,7 @@ public Publisher<Integer> apply(Integer v) throws Exception { @Test public void drainCancelRaceOnEmpty() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); @@ -1193,4 +1192,178 @@ public Flowable<Integer> apply(Integer i) throws Exception { .assertResult(1, 2, 3, 4, 5) ; } + + @Test + @SuppressWarnings("unchecked") + public void maxConcurrencyOf2() { + List<Integer>[] list = new ArrayList[100]; + for (int i = 0; i < 100; i++) { + List<Integer> lst = new ArrayList<Integer>(); + list[i] = lst; + for (int k = 1; k <= 10; k++) { + lst.add((i) * 10 + k); + } + } + + Flowable.range(1, 1000) + .buffer(10) + .concatMapEager(new Function<List<Integer>, Flowable<List<Integer>>>() { + @Override + public Flowable<List<Integer>> apply(List<Integer> v) + throws Exception { + return Flowable.just(v) + .subscribeOn(Schedulers.io()) + .doOnNext(new Consumer<List<Integer>>() { + @Override + public void accept(List<Integer> v) + throws Exception { + Thread.sleep(new Random().nextInt(20)); + } + }); + } + } + , 2, 3) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(list); + } + + @Test + public void arrayDelayErrorDefault() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + PublishProcessor<Integer> pp3 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber<Integer> ts = Flowable.concatArrayEagerDelayError(pp1, pp2, pp3) + .test(); + + ts.assertEmpty(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + assertTrue(pp3.hasSubscribers()); + + pp2.onNext(2); + pp2.onComplete(); + + ts.assertEmpty(); + + pp1.onNext(1); + + ts.assertValuesOnly(1); + + pp1.onComplete(); + + ts.assertValuesOnly(1, 2); + + pp3.onComplete(); + + ts.assertResult(1, 2); + } + + @Test + public void arrayDelayErrorMaxConcurrency() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + PublishProcessor<Integer> pp3 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber<Integer> ts = Flowable.concatArrayEagerDelayError(2, 2, pp1, pp2, pp3) + .test(); + + ts.assertEmpty(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + assertFalse(pp3.hasSubscribers()); + + pp2.onNext(2); + pp2.onComplete(); + + ts.assertEmpty(); + + pp1.onNext(1); + + ts.assertValuesOnly(1); + + pp1.onComplete(); + + assertTrue(pp3.hasSubscribers()); + + ts.assertValuesOnly(1, 2); + + pp3.onComplete(); + + ts.assertResult(1, 2); + } + + @Test + public void arrayDelayErrorMaxConcurrencyErrorDelayed() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + PublishProcessor<Integer> pp3 = PublishProcessor.create(); + + @SuppressWarnings("unchecked") + TestSubscriber<Integer> ts = Flowable.concatArrayEagerDelayError(2, 2, pp1, pp2, pp3) + .test(); + + ts.assertEmpty(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + assertFalse(pp3.hasSubscribers()); + + pp2.onNext(2); + pp2.onError(new TestException()); + + ts.assertEmpty(); + + pp1.onNext(1); + + ts.assertValuesOnly(1); + + pp1.onComplete(); + + assertTrue(pp3.hasSubscribers()); + + ts.assertValuesOnly(1, 2); + + pp3.onComplete(); + + ts.assertFailure(TestException.class, 1, 2); + } + + @Test + public void cancelActive() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + + TestSubscriber<Integer> ts = Flowable + .concatEager(Flowable.just(pp1, pp2)) + .test(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + + ts.cancel(); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + } + + @Test + public void cancelNoInnerYet() { + PublishProcessor<Flowable<Integer>> pp1 = PublishProcessor.create(); + + TestSubscriber<Integer> ts = Flowable + .concatEager(pp1) + .test(); + + assertTrue(pp1.hasSubscribers()); + + ts.cancel(); + + assertFalse(pp1.hasSubscribers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java new file mode 100644 index 0000000000..8bd29121a0 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java @@ -0,0 +1,273 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableConcatMap.SimpleScalarSubscription; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatMapTest { + + @Test + public void weakSubscriptionRequest() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + SimpleScalarSubscription<Integer> ws = new SimpleScalarSubscription<Integer>(1, ts); + ts.onSubscribe(ws); + + ws.request(0); + + ts.assertEmpty(); + + ws.request(1); + + ts.assertResult(1); + + ws.request(1); + + ts.assertResult(1); + } + + @Test + public void boundaryFusion() { + Flowable.range(1, 10000) + .observeOn(Schedulers.single()) + .map(new Function<Integer, String>() { + @Override + public String apply(Integer t) throws Exception { + String name = Thread.currentThread().getName(); + if (name.contains("RxSingleScheduler")) { + return "RxSingleScheduler"; + } + return name; + } + }) + .concatMap(new Function<String, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(String v) + throws Exception { + return Flowable.just(v); + } + }) + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void boundaryFusionDelayError() { + Flowable.range(1, 10000) + .observeOn(Schedulers.single()) + .map(new Function<Integer, String>() { + @Override + public String apply(Integer t) throws Exception { + String name = Thread.currentThread().getName(); + if (name.contains("RxSingleScheduler")) { + return "RxSingleScheduler"; + } + return name; + } + }) + .concatMapDelayError(new Function<String, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(String v) + throws Exception { + return Flowable.just(v); + } + }) + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void innerScalarRequestRace() { + final Flowable<Integer> just = Flowable.just(1); + final int n = 1000; + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Flowable<Integer>> source = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = source + .concatMap(Functions.<Flowable<Integer>>identity(), n + 1) + .test(1L); + + TestHelper.race(new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + source.onNext(just); + } + } + }, new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + ts.request(1); + } + } + }); + + ts.assertValueCount(n); + } + } + + @Test + public void innerScalarRequestRaceDelayError() { + final Flowable<Integer> just = Flowable.just(1); + final int n = 1000; + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Flowable<Integer>> source = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = source + .concatMapDelayError(Functions.<Flowable<Integer>>identity(), n + 1, true) + .test(1L); + + TestHelper.race(new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + source.onNext(just); + } + } + }, new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + ts.request(1); + } + } + }); + + ts.assertValueCount(n); + } + } + + @Test + public void pollThrows() { + Flowable.just(1) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.<Integer>flowableStripBoundary()) + .concatMap(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrowsDelayError() { + Flowable.just(1) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.<Integer>flowableStripBoundary()) + .concatMapDelayError(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.just(v); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void noCancelPrevious() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable.range(1, 5) + .concatMap(new Function<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Integer v) throws Exception { + return Flowable.just(v).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(0, counter.get()); + } + + @Test + public void delayErrorCallableTillTheEnd() { + Flowable.just(1, 2, 3, 101, 102, 23, 890, 120, 32) + .concatMapDelayError(new Function<Integer, Flowable<Integer>>() { + @Override public Flowable<Integer> apply(final Integer integer) throws Exception { + return Flowable.fromCallable(new Callable<Integer>() { + @Override public Integer call() throws Exception { + if (integer >= 100) { + throw new NullPointerException("test null exp"); + } + return integer; + } + }); + } + }) + .test() + .assertFailure(CompositeException.class, 1, 2, 3, 23, 32); + } + + @Test + public void delayErrorCallableEager() { + Flowable.just(1, 2, 3, 101, 102, 23, 890, 120, 32) + .concatMapDelayError(new Function<Integer, Flowable<Integer>>() { + @Override public Flowable<Integer> apply(final Integer integer) throws Exception { + return Flowable.fromCallable(new Callable<Integer>() { + @Override public Integer call() throws Exception { + if (integer >= 100) { + throw new NullPointerException("test null exp"); + } + return integer; + } + }); + } + }, 2, false) + .test() + .assertFailure(NullPointerException.class, 1, 2, 3); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java index 82c8765f85..c1faba86d4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -29,7 +28,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; @@ -41,7 +40,7 @@ public class FlowableConcatTest { @Test public void testConcat() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; @@ -50,14 +49,14 @@ public void testConcat() { final Flowable<String> even = Flowable.fromArray(e); Flowable<String> concat = Flowable.concat(odds, even); - concat.subscribe(observer); + concat.subscribe(subscriber); - verify(observer, times(7)).onNext(anyString()); + verify(subscriber, times(7)).onNext(anyString()); } @Test public void testConcatWithList() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; @@ -68,14 +67,14 @@ public void testConcatWithList() { list.add(odds); list.add(even); Flowable<String> concat = Flowable.concat(Flowable.fromIterable(list)); - concat.subscribe(observer); + concat.subscribe(subscriber); - verify(observer, times(7)).onNext(anyString()); + verify(subscriber, times(7)).onNext(anyString()); } @Test public void testConcatObservableOfObservables() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; @@ -83,23 +82,23 @@ public void testConcatObservableOfObservables() { final Flowable<String> odds = Flowable.fromArray(o); final Flowable<String> even = Flowable.fromArray(e); - Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { + Flowable<Flowable<String>> flowableOfFlowables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // simulate what would happen in an observable - observer.onNext(odds); - observer.onNext(even); - observer.onComplete(); + subscriber.onNext(odds); + subscriber.onNext(even); + subscriber.onComplete(); } }); - Flowable<String> concat = Flowable.concat(observableOfObservables); + Flowable<String> concat = Flowable.concat(flowableOfFlowables); - concat.subscribe(observer); + concat.subscribe(subscriber); - verify(observer, times(7)).onNext(anyString()); + verify(subscriber, times(7)).onNext(anyString()); } /** @@ -107,12 +106,12 @@ public void subscribe(Subscriber<? super Flowable<String>> observer) { */ @Test public void testSimpleAsyncConcat() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); - Flowable.concat(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)).subscribe(observer); + Flowable.concat(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)).subscribe(subscriber); try { // wait for async observables to complete @@ -122,13 +121,13 @@ public void testSimpleAsyncConcat() { throw new RuntimeException("failed waiting on threads"); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onNext("five"); - inOrder.verify(observer, times(1)).onNext("six"); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, times(1)).onNext("five"); + inOrder.verify(subscriber, times(1)).onNext("six"); } @Test @@ -147,7 +146,7 @@ public void testNestedAsyncConcatLoop() throws Throwable { */ @Test public void testNestedAsyncConcat() throws InterruptedException { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); @@ -158,17 +157,17 @@ public void testNestedAsyncConcat() throws InterruptedException { final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); - Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(final Subscriber<? super Flowable<String>> observer) { + public void subscribe(final Subscriber<? super Flowable<String>> subscriber) { final Disposable d = Disposables.empty(); - observer.onSubscribe(new Subscription() { + subscriber.onSubscribe(new Subscription() { @Override public void request(long n) { } + @Override public void cancel() { d.dispose(); @@ -182,30 +181,30 @@ public void run() { // emit first if (!d.isDisposed()) { System.out.println("Emit o1"); - observer.onNext(Flowable.unsafeCreate(o1)); + subscriber.onNext(Flowable.unsafeCreate(o1)); } // emit second if (!d.isDisposed()) { System.out.println("Emit o2"); - observer.onNext(Flowable.unsafeCreate(o2)); + subscriber.onNext(Flowable.unsafeCreate(o2)); } // wait until sometime later and emit third try { allowThird.await(); } catch (InterruptedException e) { - observer.onError(e); + subscriber.onError(e); } if (!d.isDisposed()) { System.out.println("Emit o3"); - observer.onNext(Flowable.unsafeCreate(o3)); + subscriber.onNext(Flowable.unsafeCreate(o3)); } } catch (Throwable e) { - observer.onError(e); + subscriber.onError(e); } finally { System.out.println("Done parent Flowable"); - observer.onComplete(); + subscriber.onComplete(); parentHasFinished.countDown(); } } @@ -215,7 +214,7 @@ public void run() { } }); - Flowable.concat(observableOfObservables).subscribe(observer); + Flowable.concat(observableOfObservables).subscribe(subscriber); // wait for parent to start parentHasStarted.await(); @@ -230,20 +229,20 @@ public void run() { throw new RuntimeException("failed waiting on threads", e); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onNext("five"); - inOrder.verify(observer, times(1)).onNext("six"); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, times(1)).onNext("five"); + inOrder.verify(subscriber, times(1)).onNext("six"); // we shouldn't have the following 3 yet - inOrder.verify(observer, never()).onNext("seven"); - inOrder.verify(observer, never()).onNext("eight"); - inOrder.verify(observer, never()).onNext("nine"); + inOrder.verify(subscriber, never()).onNext("seven"); + inOrder.verify(subscriber, never()).onNext("eight"); + inOrder.verify(subscriber, never()).onNext("nine"); // we should not be completed yet - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); // now allow the third allowThird.countDown(); @@ -264,17 +263,17 @@ public void run() { throw new RuntimeException("failed waiting on threads", e); } - inOrder.verify(observer, times(1)).onNext("seven"); - inOrder.verify(observer, times(1)).onNext("eight"); - inOrder.verify(observer, times(1)).onNext("nine"); + inOrder.verify(subscriber, times(1)).onNext("seven"); + inOrder.verify(subscriber, times(1)).onNext("eight"); + inOrder.verify(subscriber, times(1)).onNext("nine"); - verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onComplete(); } @Test public void testBlockedObservableOfObservables() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; @@ -285,7 +284,7 @@ public void testBlockedObservableOfObservables() { @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(callOnce, okToContinue, odds, even); Flowable<String> concatF = Flowable.concat(Flowable.unsafeCreate(observableOfObservables)); - concatF.subscribe(observer); + concatF.subscribe(subscriber); try { //Block main thread to allow observables to serve up o1. callOnce.await(); @@ -294,10 +293,10 @@ public void testBlockedObservableOfObservables() { fail(ex.getMessage()); } // The concated observable should have served up all of the odds. - verify(observer, times(1)).onNext("1"); - verify(observer, times(1)).onNext("3"); - verify(observer, times(1)).onNext("5"); - verify(observer, times(1)).onNext("7"); + verify(subscriber, times(1)).onNext("1"); + verify(subscriber, times(1)).onNext("3"); + verify(subscriber, times(1)).onNext("5"); + verify(subscriber, times(1)).onNext("7"); try { // unblock observables so it can serve up o2 and complete @@ -308,9 +307,9 @@ public void testBlockedObservableOfObservables() { fail(ex.getMessage()); } // The concatenated observable should now have served up all the evens. - verify(observer, times(1)).onNext("2"); - verify(observer, times(1)).onNext("4"); - verify(observer, times(1)).onNext("6"); + verify(subscriber, times(1)).onNext("2"); + verify(subscriber, times(1)).onNext("4"); + verify(subscriber, times(1)).onNext("6"); } @Test @@ -319,13 +318,13 @@ public void testConcatConcurrentWithInfinity() { //This observable will send "hello" MAX_VALUE time. final TestObservable<String> w2 = new TestObservable<String>("hello", Integer.MAX_VALUE); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); Flowable<String> concatF = Flowable.concat(Flowable.unsafeCreate(observableOfObservables)); - concatF.take(50).subscribe(observer); + concatF.take(50).subscribe(subscriber); //Wait for the thread to start up. try { @@ -335,13 +334,13 @@ public void testConcatConcurrentWithInfinity() { e.printStackTrace(); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(47)).onNext("hello"); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(47)).onNext("hello"); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -353,24 +352,24 @@ public void testConcatNonBlockingObservables() { final TestObservable<String> w1 = new TestObservable<String>(null, okToContinueW1, "one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(null, okToContinueW2, "four", "five", "six"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<Flowable<String>> observableOfObservables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // simulate what would happen in an observable - observer.onNext(Flowable.unsafeCreate(w1)); - observer.onNext(Flowable.unsafeCreate(w2)); - observer.onComplete(); + subscriber.onNext(Flowable.unsafeCreate(w1)); + subscriber.onNext(Flowable.unsafeCreate(w2)); + subscriber.onComplete(); } }); Flowable<String> concat = Flowable.concat(observableOfObservables); - concat.subscribe(observer); + concat.subscribe(subscriber); - verify(observer, times(0)).onComplete(); + verify(subscriber, times(0)).onComplete(); try { // release both threads @@ -383,14 +382,14 @@ public void subscribe(Subscriber<? super Flowable<String>> observer) { e.printStackTrace(); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onNext("five"); - inOrder.verify(observer, times(1)).onNext("six"); - verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, times(1)).onNext("five"); + inOrder.verify(subscriber, times(1)).onNext("six"); + verify(subscriber, times(1)).onComplete(); } @@ -404,8 +403,8 @@ public void testConcatUnsubscribe() { final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer, 0L); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber, 0L); final Flowable<String> concat = Flowable.concat(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); @@ -425,14 +424,14 @@ public void testConcatUnsubscribe() { fail(e.getMessage()); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, never()).onNext("five"); - inOrder.verify(observer, never()).onNext("six"); - inOrder.verify(observer, never()).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, never()).onNext("five"); + inOrder.verify(subscriber, never()).onNext("six"); + inOrder.verify(subscriber, never()).onComplete(); } @@ -446,8 +445,8 @@ public void testConcatUnsubscribeConcurrent() { final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer, 0L); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber, 0L); @SuppressWarnings("unchecked") TestObservable<Flowable<String>> observableOfObservables = new TestObservable<Flowable<String>>(Flowable.unsafeCreate(w1), Flowable.unsafeCreate(w2)); @@ -470,15 +469,15 @@ public void testConcatUnsubscribeConcurrent() { fail(e.getMessage()); } - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, never()).onNext("five"); - inOrder.verify(observer, never()).onNext("six"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, never()).onNext("five"); + inOrder.verify(subscriber, never()).onNext("six"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } private static class TestObservable<T> implements Publisher<T> { @@ -526,8 +525,8 @@ public void cancel() { } @Override - public void subscribe(final Subscriber<? super T> observer) { - observer.onSubscribe(s); + public void subscribe(final Subscriber<? super T> subscriber) { + subscriber.onSubscribe(s); t = new Thread(new Runnable() { @Override @@ -535,9 +534,9 @@ public void run() { try { while (count < size && subscribed) { if (null != values) { - observer.onNext(values.get(count)); + subscriber.onNext(values.get(count)); } else { - observer.onNext(seed); + subscriber.onNext(seed); } count++; //Unblock the main thread to call unsubscribe. @@ -550,7 +549,7 @@ public void run() { } } if (subscribed) { - observer.onComplete(); + subscriber.onComplete(); } } catch (InterruptedException e) { e.printStackTrace(); @@ -571,45 +570,45 @@ void waitForThreadDone() throws InterruptedException { @Test public void testMultipleObservers() { - Subscriber<Object> o1 = TestHelper.mockSubscriber(); - Subscriber<Object> o2 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); TestScheduler s = new TestScheduler(); Flowable<Long> timer = Flowable.interval(500, TimeUnit.MILLISECONDS, s).take(2); - Flowable<Long> o = Flowable.concat(timer, timer); + Flowable<Long> f = Flowable.concat(timer, timer); - o.subscribe(o1); - o.subscribe(o2); + f.subscribe(subscriber1); + f.subscribe(subscriber2); - InOrder inOrder1 = inOrder(o1); - InOrder inOrder2 = inOrder(o2); + InOrder inOrder1 = inOrder(subscriber1); + InOrder inOrder2 = inOrder(subscriber2); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); - inOrder1.verify(o1, times(1)).onNext(0L); - inOrder2.verify(o2, times(1)).onNext(0L); + inOrder1.verify(subscriber1, times(1)).onNext(0L); + inOrder2.verify(subscriber2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); - inOrder1.verify(o1, times(1)).onNext(1L); - inOrder2.verify(o2, times(1)).onNext(1L); + inOrder1.verify(subscriber1, times(1)).onNext(1L); + inOrder2.verify(subscriber2, times(1)).onNext(1L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); - inOrder1.verify(o1, times(1)).onNext(0L); - inOrder2.verify(o2, times(1)).onNext(0L); + inOrder1.verify(subscriber1, times(1)).onNext(0L); + inOrder2.verify(subscriber2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); - inOrder1.verify(o1, times(1)).onNext(1L); - inOrder2.verify(o2, times(1)).onNext(1L); + inOrder1.verify(subscriber1, times(1)).onNext(1L); + inOrder2.verify(subscriber2, times(1)).onNext(1L); - inOrder1.verify(o1, times(1)).onComplete(); - inOrder2.verify(o2, times(1)).onComplete(); + inOrder1.verify(subscriber1, times(1)).onComplete(); + inOrder2.verify(subscriber2, times(1)).onComplete(); - verify(o1, never()).onError(any(Throwable.class)); - verify(o2, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); + verify(subscriber2, never()).onError(any(Throwable.class)); } @Test @@ -636,6 +635,7 @@ public Flowable<Integer> apply(Integer v) { inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } + @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; @@ -705,7 +705,7 @@ public void testInnerBackpressureWithoutAlignedBoundaries() { // https://github.com/ReactiveX/RxJava/issues/1818 @Test public void testConcatWithNonCompliantSourceDoubleOnComplete() { - Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> s) { @@ -718,7 +718,7 @@ public void subscribe(Subscriber<? super String> s) { }); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.concat(o, o).subscribe(ts); + Flowable.concat(f, f).subscribe(ts); ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); ts.assertTerminated(); ts.assertNoErrors(); @@ -733,12 +733,12 @@ public void testIssue2890NoStackoverflow() throws InterruptedException { Function<Integer, Flowable<Integer>> func = new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { - Flowable<Integer> observable = Flowable.just(t) + Flowable<Integer> flowable = Flowable.just(t) .subscribeOn(sch) ; - FlowableProcessor<Integer> subject = UnicastProcessor.create(); - observable.subscribe(subject); - return subject; + FlowableProcessor<Integer> processor = UnicastProcessor.create(); + flowable.subscribe(processor); + return processor; } }; @@ -778,10 +778,10 @@ public void onError(Throwable e) { @Test public void testRequestOverflowDoesNotStallStream() { - Flowable<Integer> o1 = Flowable.just(1,2,3); - Flowable<Integer> o2 = Flowable.just(4,5,6); + Flowable<Integer> f1 = Flowable.just(1, 2, 3); + Flowable<Integer> f2 = Flowable.just(4, 5, 6); final AtomicBoolean completed = new AtomicBoolean(false); - o1.concatWith(o2).subscribe(new DefaultSubscriber<Integer>() { + f1.concatWith(f2).subscribe(new DefaultSubscriber<Integer>() { @Override public void onComplete() { @@ -1626,4 +1626,42 @@ public void subscribe(FlowableEmitter<Integer> s) throws Exception { assertEquals(1, calls[0]); } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousArray() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Integer> source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Flowable.concatArray(source, source, source, source, source) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousIterable() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Integer> source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Flowable.concat(Arrays.asList(source, source, source, source, source)) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletableTest.java new file mode 100644 index 0000000000..681f9d91f3 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletableTest.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Action; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.CompletableSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatWithCompletableTest { + + @Test + public void normal() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .subscribe(ts); + + ts.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.<Integer>error(new TestException()) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .subscribe(ts); + + ts.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Completable.error(new TestException())) + .subscribe(ts); + + ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .take(3) + .subscribe(ts); + + ts.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + CompletableSubject other = CompletableSubject.create(); + + TestSubscriber<Object> ts = Flowable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + ts.cancel(); + + assertFalse(other.hasObservers()); + } + + @Test + public void badSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + BooleanSubscription bs1 = new BooleanSubscription(); + s.onSubscribe(bs1); + + BooleanSubscription bs2 = new BooleanSubscription(); + s.onSubscribe(bs2); + + assertFalse(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + + s.onComplete(); + } + }.concatWith(Completable.complete()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java new file mode 100644 index 0000000000..64a7c62149 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybeTest.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.subjects.MaybeSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatWithMaybeTest { + + @Test + public void normalEmpty() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .subscribe(ts); + + ts.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void normalNonEmpty() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Maybe.just(100)) + .subscribe(ts); + + ts.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void backpressure() { + Flowable.range(1, 5) + .concatWith(Maybe.just(100)) + .test(0) + .assertEmpty() + .requestMore(3) + .assertValues(1, 2, 3) + .requestMore(2) + .assertValues(1, 2, 3, 4, 5) + .requestMore(1) + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.<Integer>error(new TestException()) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .subscribe(ts); + + ts.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Maybe.<Integer>error(new TestException())) + .subscribe(ts); + + ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + })) + .take(3) + .subscribe(ts); + + ts.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + MaybeSubject<Object> other = MaybeSubject.create(); + + TestSubscriber<Object> ts = Flowable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + ts.cancel(); + + assertFalse(other.hasObservers()); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingleTest.java new file mode 100644 index 0000000000..921f3c163a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingleTest.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.subjects.SingleSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatWithSingleTest { + + @Test + public void normal() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Single.just(100)) + .subscribe(ts); + + ts.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void backpressure() { + Flowable.range(1, 5) + .concatWith(Single.just(100)) + .test(0) + .assertEmpty() + .requestMore(3) + .assertValues(1, 2, 3) + .requestMore(2) + .assertValues(1, 2, 3, 4, 5) + .requestMore(1) + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.<Integer>error(new TestException()) + .concatWith(Single.just(100)) + .subscribe(ts); + + ts.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Single.<Integer>error(new TestException())) + .subscribe(ts); + + ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5) + .concatWith(Single.just(100)) + .take(3) + .subscribe(ts); + + ts.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + SingleSubject<Object> other = SingleSubject.create(); + + TestSubscriber<Object> ts = Flowable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + ts.cancel(); + + assertFalse(other.hasObservers()); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java index a84389310a..998569148d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCountTest.java @@ -39,7 +39,6 @@ public void simple() { } - @Test public void dispose() { TestHelper.checkDisposed(Flowable.just(1).count()); @@ -51,15 +50,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Long>>() { @Override - public Flowable<Long> apply(Flowable<Object> o) throws Exception { - return o.count().toFlowable(); + public Flowable<Long> apply(Flowable<Object> f) throws Exception { + return f.count().toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, SingleSource<Long>>() { @Override - public SingleSource<Long> apply(Flowable<Object> o) throws Exception { - return o.count(); + public SingleSource<Long> apply(Flowable<Object> f) throws Exception { + return f.count(); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java index 1289087d65..899f145ca2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCreateTest.java @@ -16,7 +16,9 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Test; import org.reactivestreams.*; @@ -27,7 +29,6 @@ import io.reactivex.functions.Cancellable; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableCreateTest { @@ -47,151 +48,189 @@ public void subscribe(FlowableEmitter<Object> s) throws Exception { } @Test public void basic() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e.setDisposable(d); - - e.onNext(1); - e.onNext(2); - e.onNext(3); - e.onComplete(); - e.onError(new TestException()); - e.onNext(4); - e.onError(new TestException()); - e.onComplete(); - } - }, BackpressureStrategy.BUFFER) - .test() - .assertResult(1, 2, 3); + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onNext(1); + e.onNext(2); + e.onNext(3); + e.onComplete(); + e.onError(new TestException("first")); + e.onNext(4); + e.onError(new TestException("second")); + e.onComplete(); + } + }, BackpressureStrategy.BUFFER) + .test() + .assertResult(1, 2, 3); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class, "first"); + TestHelper.assertUndeliverable(errors, 1, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithCancellable() { - final Disposable d1 = Disposables.empty(); - final Disposable d2 = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d1 = Disposables.empty(); + final Disposable d2 = Disposables.empty(); - Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e.setDisposable(d1); - e.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - d2.dispose(); - } - }); - - e.onNext(1); - e.onNext(2); - e.onNext(3); - e.onComplete(); - e.onError(new TestException()); - e.onNext(4); - e.onError(new TestException()); - e.onComplete(); - } - }, BackpressureStrategy.BUFFER) - .test() - .assertResult(1, 2, 3); + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e.setDisposable(d1); + e.setCancellable(new Cancellable() { + @Override + public void cancel() throws Exception { + d2.dispose(); + } + }); - assertTrue(d1.isDisposed()); - assertTrue(d2.isDisposed()); + e.onNext(1); + e.onNext(2); + e.onNext(3); + e.onComplete(); + e.onError(new TestException("first")); + e.onNext(4); + e.onError(new TestException("second")); + e.onComplete(); + } + }, BackpressureStrategy.BUFFER) + .test() + .assertResult(1, 2, 3); + + assertTrue(d1.isDisposed()); + assertTrue(d2.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "first"); + TestHelper.assertUndeliverable(errors, 1, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e.setDisposable(d); - - e.onNext(1); - e.onNext(2); - e.onNext(3); - e.onError(new TestException()); - e.onComplete(); - e.onNext(4); - e.onError(new TestException()); - } - }, BackpressureStrategy.BUFFER) - .test() - .assertFailure(TestException.class, 1, 2, 3); + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e.setDisposable(d); - assertTrue(d.isDisposed()); + e.onNext(1); + e.onNext(2); + e.onNext(3); + e.onError(new TestException()); + e.onComplete(); + e.onNext(4); + e.onError(new TestException("second")); + } + }, BackpressureStrategy.BUFFER) + .test() + .assertFailure(TestException.class, 1, 2, 3); + + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicSerialized() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - - e.setDisposable(d); - - e.onNext(1); - e.onNext(2); - e.onNext(3); - e.onComplete(); - e.onError(new TestException()); - e.onNext(4); - e.onError(new TestException()); - e.onComplete(); - } - }, BackpressureStrategy.BUFFER) - .test() - .assertResult(1, 2, 3); + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + + e.setDisposable(d); + + e.onNext(1); + e.onNext(2); + e.onNext(3); + e.onComplete(); + e.onError(new TestException("first")); + e.onNext(4); + e.onError(new TestException("second")); + e.onComplete(); + } + }, BackpressureStrategy.BUFFER) + .test() + .assertResult(1, 2, 3); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class, "first"); + TestHelper.assertUndeliverable(errors, 1, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithErrorSerialized() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - - e.setDisposable(d); - - e.onNext(1); - e.onNext(2); - e.onNext(3); - e.onError(new TestException()); - e.onComplete(); - e.onNext(4); - e.onError(new TestException()); - } - }, BackpressureStrategy.BUFFER) - .test() - .assertFailure(TestException.class, 1, 2, 3); + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + + e.setDisposable(d); + + e.onNext(1); + e.onNext(2); + e.onNext(3); + e.onError(new TestException()); + e.onComplete(); + e.onNext(4); + e.onError(new TestException("second")); + } + }, BackpressureStrategy.BUFFER) + .test() + .assertFailure(TestException.class, 1, 2, 3); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } } @Test public void wrap() { Flowable.fromPublisher(new Publisher<Integer>() { @Override - public void subscribe(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onNext(3); - observer.onNext(4); - observer.onNext(5); - observer.onComplete(); + public void subscribe(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onNext(3); + subscriber.onNext(4); + subscriber.onNext(5); + subscriber.onComplete(); } }) .test() @@ -202,14 +241,14 @@ public void subscribe(Subscriber<? super Integer> observer) { public void unsafe() { Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onNext(3); - observer.onNext(4); - observer.onNext(5); - observer.onComplete(); + public void subscribe(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onNext(3); + subscriber.onNext(4); + subscriber.onNext(5); + subscriber.onComplete(); } }) .test() @@ -223,237 +262,308 @@ public void unsafeWithFlowable() { @Test public void createNullValueBuffer() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + final Throwable[] error = { null }; + + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.BUFFER) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.BUFFER) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueLatest() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.LATEST) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.LATEST) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueError() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.ERROR) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.ERROR) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueDrop() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.DROP) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.DROP) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueMissing() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.MISSING) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.MISSING) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueBufferSerialized() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.BUFFER) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.BUFFER) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueLatestSerialized() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.LATEST) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.LATEST) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueErrorSerialized() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.ERROR) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.ERROR) + .test() + .assertFailure(NullPointerException.class); - assertNull(error[0]); + assertNull(error[0]); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueDropSerialized() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.DROP) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.DROP) + .test() + .assertFailure(NullPointerException.class); - assertNull(error[0]); + assertNull(error[0]); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void createNullValueMissingSerialized() { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, BackpressureStrategy.MISSING) - .test() - .assertFailure(NullPointerException.class); + }, BackpressureStrategy.MISSING) + .test() + .assertFailure(NullPointerException.class); + + assertNull(error[0]); - assertNull(error[0]); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -480,14 +590,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }, m); List<Throwable> errors = TestHelper.trackPluginErrors(); try { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { source .test() .assertFailure(Throwable.class); @@ -521,11 +631,11 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }, m); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { source .test() .assertResult(); @@ -589,7 +699,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }, m) .test() @@ -629,25 +739,32 @@ public void subscribe(FlowableEmitter<Object> e) throws Exception { @Test public void createNullValue() { for (BackpressureStrategy m : BackpressureStrategy.values()) { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, m) - .test() - .assertFailure(NullPointerException.class); + }, m) + .test() + .assertFailure(NullPointerException.class); - assertNull(error[0]); + assertNull(error[0]); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } } @@ -675,7 +792,7 @@ public void subscribe(FlowableEmitter<Object> e) throws Exception { }, m) .subscribe(new FlowableSubscriber<Object>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @Override @@ -713,7 +830,7 @@ public void subscribe(FlowableEmitter<Object> e) throws Exception { }, m) .subscribe(new FlowableSubscriber<Object>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @Override @@ -735,26 +852,33 @@ public void onComplete() { @Test public void createNullValueSerialized() { for (BackpressureStrategy m : BackpressureStrategy.values()) { - final Throwable[] error = { null }; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Throwable[] error = { null }; - Flowable.create(new FlowableOnSubscribe<Integer>() { - @Override - public void subscribe(FlowableEmitter<Integer> e) throws Exception { - e = e.serialize(); - try { - e.onNext(null); - e.onNext(1); - e.onError(new TestException()); - e.onComplete(); - } catch (Throwable ex) { - error[0] = ex; + Flowable.create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> e) throws Exception { + e = e.serialize(); + try { + e.onNext(null); + e.onNext(1); + e.onError(new TestException()); + e.onComplete(); + } catch (Throwable ex) { + error[0] = ex; + } } - } - }, m) - .test() - .assertFailure(NullPointerException.class); + }, m) + .test() + .assertFailure(NullPointerException.class); - assertNull(error[0]); + assertNull(error[0]); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } } @@ -783,25 +907,25 @@ public void subscribe(FlowableEmitter<Object> e) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { f.onNext(1); } } }; - TestHelper.race(r1, r1, Schedulers.single()); + TestHelper.race(r1, r1); } }, m) - .take(1000) + .take(TestHelper.RACE_DEFAULT_LOOPS) .test() - .assertSubscribed().assertValueCount(1000).assertComplete().assertNoErrors(); + .assertSubscribed().assertValueCount(TestHelper.RACE_DEFAULT_LOOPS).assertComplete().assertNoErrors(); } } @Test public void serializedConcurrentOnNextOnComplete() { for (BackpressureStrategy m : BackpressureStrategy.values()) { - TestSubscriber<Object> to = Flowable.create(new FlowableOnSubscribe<Object>() { + TestSubscriber<Object> ts = Flowable.create(new FlowableOnSubscribe<Object>() { @Override public void subscribe(FlowableEmitter<Object> e) throws Exception { final FlowableEmitter<Object> f = e.serialize(); @@ -825,14 +949,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }, m) .test() .assertSubscribed().assertComplete() .assertNoErrors(); - int c = to.valueCount(); + int c = ts.valueCount(); assertTrue("" + c, c >= 100); } } @@ -877,7 +1001,6 @@ public void cancel() throws Exception { } } - @Test public void tryOnError() { for (BackpressureStrategy strategy : BackpressureStrategy.values()) { @@ -932,4 +1055,27 @@ public void subscribe(FlowableEmitter<Object> e) throws Exception { } } } + + @SuppressWarnings("rawtypes") + @Test + public void emittersHasToString() { + Map<BackpressureStrategy, Class<? extends FlowableEmitter>> emitterMap = + new HashMap<BackpressureStrategy, Class<? extends FlowableEmitter>>(); + + emitterMap.put(BackpressureStrategy.MISSING, FlowableCreate.MissingEmitter.class); + emitterMap.put(BackpressureStrategy.ERROR, FlowableCreate.ErrorAsyncEmitter.class); + emitterMap.put(BackpressureStrategy.DROP, FlowableCreate.DropAsyncEmitter.class); + emitterMap.put(BackpressureStrategy.LATEST, FlowableCreate.LatestAsyncEmitter.class); + emitterMap.put(BackpressureStrategy.BUFFER, FlowableCreate.BufferAsyncEmitter.class); + + for (final Map.Entry<BackpressureStrategy, Class<? extends FlowableEmitter>> entry : emitterMap.entrySet()) { + Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> emitter) throws Exception { + assertTrue(emitter.toString().contains(entry.getValue().getSimpleName())); + assertTrue(emitter.serialize().toString().contains(entry.getValue().getSimpleName())); + } + }, entry.getKey()).test().assertEmpty(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java index db108689ce..aacff6e5af 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java @@ -18,32 +18,34 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.disposables.*; +import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableDebounceTimed.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.PublishProcessor; +import io.reactivex.processors.*; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.TestSubscriber; public class FlowableDebounceTest { private TestScheduler scheduler; - private Subscriber<String> observer; + private Subscriber<String> Subscriber; private Scheduler.Worker innerScheduler; @Before public void before() { scheduler = new TestScheduler(); - observer = TestHelper.mockSubscriber(); + Subscriber = TestHelper.mockSubscriber(); innerScheduler = scheduler.createWorker(); } @@ -51,25 +53,25 @@ public void before() { public void testDebounceWithCompleted() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. - publishNext(observer, 400, "two"); // Should be published since "three" will arrive after the timeout expires. - publishNext(observer, 900, "three"); // Should be skipped since onComplete will arrive before the timeout expires. - publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. + publishNext(subscriber, 400, "two"); // Should be published since "three" will arrive after the timeout expires. + publishNext(subscriber, 900, "three"); // Should be skipped since onComplete will arrive before the timeout expires. + publishCompleted(subscriber, 1000); // Should be published as soon as the timeout expires. } }); Flowable<String> sampled = source.debounce(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(Subscriber); // must go to 800 since it must be 400 after when two is sent, which is at 400 scheduler.advanceTimeTo(800, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(Subscriber, times(1)).onNext("two"); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(Subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -77,29 +79,29 @@ public void subscribe(Subscriber<? super String> observer) { public void testDebounceNeverEmits() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // all should be skipped since they are happening faster than the 200ms timeout - publishNext(observer, 100, "a"); // Should be skipped - publishNext(observer, 200, "b"); // Should be skipped - publishNext(observer, 300, "c"); // Should be skipped - publishNext(observer, 400, "d"); // Should be skipped - publishNext(observer, 500, "e"); // Should be skipped - publishNext(observer, 600, "f"); // Should be skipped - publishNext(observer, 700, "g"); // Should be skipped - publishNext(observer, 800, "h"); // Should be skipped - publishCompleted(observer, 900); // Should be published as soon as the timeout expires. + publishNext(subscriber, 100, "a"); // Should be skipped + publishNext(subscriber, 200, "b"); // Should be skipped + publishNext(subscriber, 300, "c"); // Should be skipped + publishNext(subscriber, 400, "d"); // Should be skipped + publishNext(subscriber, 500, "e"); // Should be skipped + publishNext(subscriber, 600, "f"); // Should be skipped + publishNext(subscriber, 700, "g"); // Should be skipped + publishNext(subscriber, 800, "h"); // Should be skipped + publishCompleted(subscriber, 900); // Should be published as soon as the timeout expires. } }); Flowable<String> sampled = source.debounce(200, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(0)).onNext(anyString()); + InOrder inOrder = inOrder(Subscriber); + inOrder.verify(Subscriber, times(0)).onNext(anyString()); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(Subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -107,51 +109,51 @@ public void subscribe(Subscriber<? super String> observer) { public void testDebounceWithError() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); Exception error = new TestException(); - publishNext(observer, 100, "one"); // Should be published since "two" will arrive after the timeout expires. - publishNext(observer, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. - publishError(observer, 700, error); // Should be published as soon as the timeout expires. + publishNext(subscriber, 100, "one"); // Should be published since "two" will arrive after the timeout expires. + publishNext(subscriber, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. + publishError(subscriber, 700, error); // Should be published as soon as the timeout expires. } }); Flowable<String> sampled = source.debounce(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(Subscriber); scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(Subscriber); // 100 + 400 means it triggers at 500 scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onNext("one"); + inOrder.verify(Subscriber).onNext("one"); scheduler.advanceTimeTo(701, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verify(Subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); } - private <T> void publishCompleted(final Subscriber<T> observer, long delay) { + private <T> void publishCompleted(final Subscriber<T> subscriber, long delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishError(final Subscriber<T> observer, long delay, final Exception error) { + private <T> void publishError(final Subscriber<T> subscriber, long delay, final Exception error) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onError(error); + subscriber.onError(error); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishNext(final Subscriber<T> observer, final long delay, final T value) { + private <T> void publishNext(final Subscriber<T> subscriber, final long delay, final T value) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } @@ -168,10 +170,10 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.debounce(debounceSel).subscribe(o); + source.debounce(debounceSel).subscribe(subscriber); source.onNext(1); debouncer.onNext(1); @@ -185,12 +187,12 @@ public Flowable<Integer> apply(Integer t1) { source.onNext(5); source.onComplete(); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(4); - inOrder.verify(o).onNext(5); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(4); + inOrder.verify(subscriber).onNext(5); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -204,15 +206,15 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.debounce(debounceSel).subscribe(o); + source.debounce(debounceSel).subscribe(subscriber); source.onNext(1); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } @Test @@ -226,33 +228,35 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.debounce(debounceSel).subscribe(o); + source.debounce(debounceSel).subscribe(subscriber); source.onNext(1); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } + @Test public void debounceTimedLastIsNotLost() { PublishProcessor<Integer> source = PublishProcessor.create(); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.debounce(100, TimeUnit.MILLISECONDS, scheduler).subscribe(o); + source.debounce(100, TimeUnit.MILLISECONDS, scheduler).subscribe(subscriber); source.onNext(1); source.onComplete(); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void debounceSelectorLastIsNotLost() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -266,18 +270,18 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.debounce(debounceSel).subscribe(o); + source.debounce(debounceSel).subscribe(subscriber); source.onNext(1); source.onComplete(); debouncer.onComplete(); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -338,12 +342,12 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onNext(1); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .debounce(1, TimeUnit.SECONDS, new TestScheduler()) @@ -360,8 +364,8 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { public void badSourceSelector() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.debounce(new Function<Integer, Flowable<Long>>() { + public Object apply(Flowable<Integer> f) throws Exception { + return f.debounce(new Function<Integer, Flowable<Long>>() { @Override public Flowable<Long> apply(Integer v) throws Exception { return Flowable.timer(1, TimeUnit.SECONDS); @@ -372,11 +376,11 @@ public Flowable<Long> apply(Integer v) throws Exception { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(final Flowable<Integer> o) throws Exception { + public Object apply(final Flowable<Integer> f) throws Exception { return Flowable.just(1).debounce(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { - return o; + return f; } }); } @@ -407,4 +411,149 @@ public void backpressureNoRequestTimed() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(MissingBackpressureException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.debounce(Functions.justFunction(Flowable.never())); + } + }); + } + + @Test + public void disposeInOnNext() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + BehaviorProcessor.createDefault(1) + .debounce(new Function<Integer, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Integer o) throws Exception { + ts.cancel(); + return Flowable.never(); + } + }) + .subscribeWith(ts) + .assertEmpty(); + + assertTrue(ts.isDisposed()); + } + + @Test + public void disposedInOnComplete() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ts.cancel(); + subscriber.onComplete(); + } + } + .debounce(Functions.justFunction(Flowable.never())) + .subscribeWith(ts) + .assertEmpty(); + } + + @Test + public void emitLate() { + final AtomicReference<Subscriber<? super Integer>> ref = new AtomicReference<Subscriber<? super Integer>>(); + + TestSubscriber<Integer> ts = Flowable.range(1, 2) + .debounce(new Function<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Integer o) throws Exception { + if (o != 1) { + return Flowable.never(); + } + return new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }; + } + }) + .test(); + + ref.get().onNext(1); + + ts + .assertResult(2); + } + + @Test + public void badRequestReported() { + TestHelper.assertBadRequestReported(Flowable.never().debounce(Functions.justFunction(Flowable.never()))); + } + + @Test + public void timedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f.debounce(1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedDisposedIgnoredBySource() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + new Flowable<Integer>() { + @Override + protected void subscribeActual( + org.reactivestreams.Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + ts.cancel(); + s.onNext(1); + s.onComplete(); + } + } + .debounce(1, TimeUnit.SECONDS) + .subscribe(ts); + } + + @Test + public void timedBadRequest() { + TestHelper.assertBadRequestReported(Flowable.never().debounce(1, TimeUnit.SECONDS)); + } + + @Test + public void timedLateEmit() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + DebounceTimedSubscriber<Integer> sub = new DebounceTimedSubscriber<Integer>( + ts, 1, TimeUnit.SECONDS, new TestScheduler().createWorker()); + + sub.onSubscribe(new BooleanSubscription()); + + DebounceEmitter<Integer> de = new DebounceEmitter<Integer>(1, 50, sub); + de.emit(); + de.emit(); + + ts.assertEmpty(); + } + + @Test + public void timedError() { + Flowable.error(new TestException()) + .debounce(1, TimeUnit.SECONDS) + .test() + .assertFailure(TestException.class); + } + + @Test + public void debounceOnEmpty() { + Flowable.empty().debounce(new Function<Object, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Object o) { + return Flowable.just(new Object()); + } + }).subscribe(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java index 24b41502e9..4c75d500e8 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDefaultIfEmptyTest.java @@ -27,38 +27,38 @@ public class FlowableDefaultIfEmptyTest { @Test public void testDefaultIfEmpty() { Flowable<Integer> source = Flowable.just(1, 2, 3); - Flowable<Integer> observable = source.defaultIfEmpty(10); + Flowable<Integer> flowable = source.defaultIfEmpty(10); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, never()).onNext(10); - verify(observer).onNext(1); - verify(observer).onNext(2); - verify(observer).onNext(3); - verify(observer).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(10); + verify(subscriber).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber).onNext(3); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testDefaultIfEmptyWithEmpty() { Flowable<Integer> source = Flowable.empty(); - Flowable<Integer> observable = source.defaultIfEmpty(10); + Flowable<Integer> flowable = source.defaultIfEmpty(10); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer).onNext(10); - verify(observer).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(10); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @Ignore("Subscribers should not throw") public void testEmptyButClientThrows() { - final Subscriber<Integer> o = TestHelper.mockSubscriber(); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); Flowable.<Integer>empty().defaultIfEmpty(1).subscribe(new DefaultSubscriber<Integer>() { @Override @@ -68,18 +68,18 @@ public void onNext(Integer t) { @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any(Integer.class)); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any(Integer.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -97,7 +97,7 @@ public void testBackpressureEmpty() { @Test public void testBackpressureNonEmpty() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); - Flowable.just(1,2,3).defaultIfEmpty(1).subscribe(ts); + Flowable.just(1, 2, 3).defaultIfEmpty(1).subscribe(ts); ts.assertNoValues(); ts.assertNotTerminated(); ts.request(2); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDeferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDeferTest.java index 2d3cabc7a9..8d46e9c34e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDeferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDeferTest.java @@ -22,7 +22,6 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; -import io.reactivex.subscribers.DefaultSubscriber; @SuppressWarnings("unchecked") public class FlowableDeferTest { @@ -40,25 +39,25 @@ public void testDefer() throws Throwable { verifyZeroInteractions(factory); - Subscriber<String> firstObserver = TestHelper.mockSubscriber(); - deferred.subscribe(firstObserver); + Subscriber<String> firstSubscriber = TestHelper.mockSubscriber(); + deferred.subscribe(firstSubscriber); verify(factory, times(1)).call(); - verify(firstObserver, times(1)).onNext("one"); - verify(firstObserver, times(1)).onNext("two"); - verify(firstObserver, times(0)).onNext("three"); - verify(firstObserver, times(0)).onNext("four"); - verify(firstObserver, times(1)).onComplete(); + verify(firstSubscriber, times(1)).onNext("one"); + verify(firstSubscriber, times(1)).onNext("two"); + verify(firstSubscriber, times(0)).onNext("three"); + verify(firstSubscriber, times(0)).onNext("four"); + verify(firstSubscriber, times(1)).onComplete(); - Subscriber<String> secondObserver = TestHelper.mockSubscriber(); - deferred.subscribe(secondObserver); + Subscriber<String> secondSubscriber = TestHelper.mockSubscriber(); + deferred.subscribe(secondSubscriber); verify(factory, times(2)).call(); - verify(secondObserver, times(0)).onNext("one"); - verify(secondObserver, times(0)).onNext("two"); - verify(secondObserver, times(1)).onNext("three"); - verify(secondObserver, times(1)).onNext("four"); - verify(secondObserver, times(1)).onComplete(); + verify(secondSubscriber, times(0)).onNext("one"); + verify(secondSubscriber, times(0)).onNext("two"); + verify(secondSubscriber, times(1)).onNext("three"); + verify(secondSubscriber, times(1)).onNext("four"); + verify(secondSubscriber, times(1)).onComplete(); } @@ -70,12 +69,12 @@ public void testDeferFunctionThrows() throws Exception { Flowable<String> result = Flowable.defer(factory); - DefaultSubscriber<String> o = mock(DefaultSubscriber.class); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any(String.class)); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onComplete(); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOtherTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOtherTest.java index 9e23acf0ee..e61c3f7905 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOtherTest.java @@ -316,8 +316,8 @@ public void otherNull() { public void badSourceOther() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return Flowable.just(1).delaySubscription(o); + public Object apply(Flowable<Integer> f) throws Exception { + return Flowable.just(1).delaySubscription(f); } }, false, 1, 1, 1); } @@ -327,8 +327,8 @@ public void afterDelayNoInterrupt() { ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); try { for (Scheduler s : new Scheduler[] { Schedulers.single(), Schedulers.computation(), Schedulers.newThread(), Schedulers.io(), Schedulers.from(exec) }) { - final TestSubscriber<Boolean> observer = TestSubscriber.create(); - observer.withTag(s.getClass().getSimpleName()); + final TestSubscriber<Boolean> ts = TestSubscriber.create(); + ts.withTag(s.getClass().getSimpleName()); Flowable.<Boolean>create(new FlowableOnSubscribe<Boolean>() { @Override @@ -338,10 +338,10 @@ public void subscribe(FlowableEmitter<Boolean> emitter) throws Exception { } }, BackpressureStrategy.MISSING) .delaySubscription(100, TimeUnit.MILLISECONDS, s) - .subscribe(observer); + .subscribe(ts); - observer.awaitTerminalEvent(); - observer.assertValue(false); + ts.awaitTerminalEvent(); + ts.assertValue(false); } } finally { exec.shutdown(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java index 40e6ff58fb..cf1817e0e4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -34,15 +33,15 @@ import io.reactivex.subscribers.*; public class FlowableDelayTest { - private Subscriber<Long> observer; - private Subscriber<Long> observer2; + private Subscriber<Long> subscriber; + private Subscriber<Long> subscriber2; private TestScheduler scheduler; @Before public void before() { - observer = TestHelper.mockSubscriber(); - observer2 = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); + subscriber2 = TestHelper.mockSubscriber(); scheduler = new TestScheduler(); } @@ -51,69 +50,69 @@ public void before() { public void testDelay() { Flowable<Long> source = Flowable.interval(1L, TimeUnit.SECONDS, scheduler).take(3); Flowable<Long> delayed = source.delay(500L, TimeUnit.MILLISECONDS, scheduler); - delayed.subscribe(observer); + delayed.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(0L); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(0L); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2400L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(1L); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(3400L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(3500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(2L); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(2L); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testLongDelay() { Flowable<Long> source = Flowable.interval(1L, TimeUnit.SECONDS, scheduler).take(3); Flowable<Long> delayed = source.delay(5L, TimeUnit.SECONDS, scheduler); - delayed.subscribe(observer); + delayed.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(5999L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(6000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(0L); + inOrder.verify(subscriber, times(1)).onNext(0L); scheduler.advanceTimeTo(6999L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); + inOrder.verify(subscriber, never()).onNext(anyLong()); scheduler.advanceTimeTo(7000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); + inOrder.verify(subscriber, times(1)).onNext(1L); scheduler.advanceTimeTo(7999L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); + inOrder.verify(subscriber, never()).onNext(anyLong()); scheduler.advanceTimeTo(8000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(2L); - inOrder.verify(observer, times(1)).onComplete(); - inOrder.verify(observer, never()).onNext(anyLong()); - inOrder.verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(2L); + inOrder.verify(subscriber, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyLong()); + inOrder.verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -129,103 +128,103 @@ public Long apply(Long value) { } }); Flowable<Long> delayed = source.delay(1L, TimeUnit.SECONDS, scheduler); - delayed.subscribe(observer); + delayed.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(1999L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); scheduler.advanceTimeTo(5000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyLong()); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test public void testDelayWithMultipleSubscriptions() { Flowable<Long> source = Flowable.interval(1L, TimeUnit.SECONDS, scheduler).take(3); Flowable<Long> delayed = source.delay(500L, TimeUnit.MILLISECONDS, scheduler); - delayed.subscribe(observer); - delayed.subscribe(observer2); + delayed.subscribe(subscriber); + delayed.subscribe(subscriber2); - InOrder inOrder = inOrder(observer); - InOrder inOrder2 = inOrder(observer2); + InOrder inOrder = inOrder(subscriber); + InOrder inOrder2 = inOrder(subscriber2); scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(anyLong()); - verify(observer2, never()).onNext(anyLong()); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber2, never()).onNext(anyLong()); scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(0L); - inOrder2.verify(observer2, times(1)).onNext(0L); + inOrder.verify(subscriber, times(1)).onNext(0L); + inOrder2.verify(subscriber2, times(1)).onNext(0L); scheduler.advanceTimeTo(2499L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - inOrder2.verify(observer2, never()).onNext(anyLong()); + inOrder.verify(subscriber, never()).onNext(anyLong()); + inOrder2.verify(subscriber2, never()).onNext(anyLong()); scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - inOrder2.verify(observer2, times(1)).onNext(1L); + inOrder.verify(subscriber, times(1)).onNext(1L); + inOrder2.verify(subscriber2, times(1)).onNext(1L); - verify(observer, never()).onComplete(); - verify(observer2, never()).onComplete(); + verify(subscriber, never()).onComplete(); + verify(subscriber2, never()).onComplete(); scheduler.advanceTimeTo(3500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(2L); - inOrder2.verify(observer2, times(1)).onNext(2L); - inOrder.verify(observer, never()).onNext(anyLong()); - inOrder2.verify(observer2, never()).onNext(anyLong()); - inOrder.verify(observer, times(1)).onComplete(); - inOrder2.verify(observer2, times(1)).onComplete(); - - verify(observer, never()).onError(any(Throwable.class)); - verify(observer2, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(2L); + inOrder2.verify(subscriber2, times(1)).onNext(2L); + inOrder.verify(subscriber, never()).onNext(anyLong()); + inOrder2.verify(subscriber2, never()).onNext(anyLong()); + inOrder.verify(subscriber, times(1)).onComplete(); + inOrder2.verify(subscriber2, times(1)).onComplete(); + + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber2, never()).onError(any(Throwable.class)); } @Test public void testDelaySubscription() { Flowable<Integer> result = Flowable.just(1, 2, 3).delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); - inOrder.verify(o, never()).onNext(any()); - inOrder.verify(o, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(any()); + inOrder.verify(subscriber, never()).onComplete(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - inOrder.verify(o, times(1)).onNext(1); - inOrder.verify(o, times(1)).onNext(2); - inOrder.verify(o, times(1)).onNext(3); - inOrder.verify(o, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onNext(3); + inOrder.verify(subscriber, times(1)).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testDelaySubscriptionCancelBeforeTime() { Flowable<Integer> result = Flowable.just(1, 2, 3).delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); result.subscribe(ts); ts.dispose(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -245,22 +244,22 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); for (int i = 0; i < n; i++) { source.onNext(i); delays.get(i).onNext(i); - inOrder.verify(o).onNext(i); + inOrder.verify(subscriber).onNext(i); } source.onComplete(); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -275,18 +274,18 @@ public Flowable<Integer> apply(Integer t1) { return delay; } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); source.onNext(1); delay.onNext(1); delay.onNext(2); - inOrder.verify(o).onNext(1); + inOrder.verify(subscriber).onNext(1); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -301,18 +300,18 @@ public Flowable<Integer> apply(Integer t1) { return delay; } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); source.onNext(1); source.onError(new TestException()); delay.onNext(1); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -326,16 +325,16 @@ public Flowable<Integer> apply(Integer t1) { throw new TestException(); } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); source.onNext(1); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -350,17 +349,17 @@ public Flowable<Integer> apply(Integer t1) { return delay; } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); source.onNext(1); delay.onError(new TestException()); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -375,10 +374,10 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delay, delayFunc).subscribe(o); + source.delay(delay, delayFunc).subscribe(subscriber); source.onNext(1); delay.onNext(1); @@ -386,10 +385,10 @@ public Flowable<Integer> apply(Integer t1) { source.onNext(2); delay.onNext(2); - inOrder.verify(o).onNext(2); + inOrder.verify(subscriber).onNext(2); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -410,20 +409,20 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(Flowable.defer(subFunc), delayFunc).subscribe(o); + source.delay(Flowable.defer(subFunc), delayFunc).subscribe(subscriber); source.onNext(1); delay.onNext(1); source.onNext(2); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -444,20 +443,20 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(Flowable.defer(subFunc), delayFunc).subscribe(o); + source.delay(Flowable.defer(subFunc), delayFunc).subscribe(subscriber); source.onNext(1); delay.onError(new TestException()); source.onNext(2); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -471,18 +470,18 @@ public Flowable<Integer> apply(Integer t1) { return Flowable.empty(); } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(delayFunc).subscribe(o); + source.delay(delayFunc).subscribe(subscriber); source.onNext(1); source.onComplete(); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -504,10 +503,10 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.delay(Flowable.defer(subFunc), delayFunc).subscribe(o); + source.delay(Flowable.defer(subFunc), delayFunc).subscribe(subscriber); source.onNext(1); sdelay.onComplete(); @@ -515,10 +514,10 @@ public Flowable<Integer> apply(Integer t1) { source.onNext(2); delay.onNext(2); - inOrder.verify(o).onNext(2); + inOrder.verify(subscriber).onNext(2); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -535,40 +534,40 @@ public Flowable<Long> apply(Long t1) { }; Flowable<Long> delayed = source.delay(delayFunc); - delayed.subscribe(observer); + delayed.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(0L); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(0L); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2400L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(1L); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(3400L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(3500L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(2L); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(2L); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -589,27 +588,27 @@ public Flowable<Integer> apply(Integer t1) { } }); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); for (int i = 0; i < n; i++) { source.onNext(i); } source.onComplete(); - inOrder.verify(o, never()).onNext(anyInt()); - inOrder.verify(o, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onComplete(); for (int i = n - 1; i >= 0; i--) { subjects.get(i).onComplete(); - inOrder.verify(o).onNext(i); + inOrder.verify(subscriber).onNext(i); } - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -624,11 +623,11 @@ public void accept(Notification<Integer> t1) { } }); - TestSubscriber<Integer> observer = new TestSubscriber<Integer>(); - delayed.subscribe(observer); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + delayed.subscribe(ts); // all will be delivered after 500ms since range does not delay between them scheduler.advanceTimeBy(500L, TimeUnit.MILLISECONDS); - observer.assertValues(1, 2, 3, 4, 5); + ts.assertValues(1, 2, 3, 4, 5); } @Test @@ -768,17 +767,17 @@ public Integer apply(Integer t) { public void testErrorRunsBeforeOnNext() { TestScheduler test = new TestScheduler(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - ps.delay(1, TimeUnit.SECONDS, test).subscribe(ts); + pp.delay(1, TimeUnit.SECONDS, test).subscribe(ts); - ps.onNext(1); + pp.onNext(1); test.advanceTimeBy(500, TimeUnit.MILLISECONDS); - ps.onError(new TestException()); + pp.onError(new TestException()); test.advanceTimeBy(1, TimeUnit.SECONDS); @@ -789,7 +788,7 @@ public void testErrorRunsBeforeOnNext() { @Test public void testDelaySupplierSimple() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); Flowable<Integer> source = Flowable.range(1, 5); @@ -798,7 +797,7 @@ public void testDelaySupplierSimple() { source.delaySubscription(Flowable.defer(new Callable<Publisher<Integer>>() { @Override public Publisher<Integer> call() { - return ps; + return pp; } })).subscribe(ts); @@ -806,7 +805,7 @@ public Publisher<Integer> call() { ts.assertNoErrors(); ts.assertNotComplete(); - ps.onNext(1); + pp.onNext(1); ts.assertValues(1, 2, 3, 4, 5); ts.assertComplete(); @@ -815,7 +814,7 @@ public Publisher<Integer> call() { @Test public void testDelaySupplierCompletes() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); Flowable<Integer> source = Flowable.range(1, 5); @@ -824,7 +823,7 @@ public void testDelaySupplierCompletes() { source.delaySubscription(Flowable.defer(new Callable<Publisher<Integer>>() { @Override public Publisher<Integer> call() { - return ps; + return pp; } })).subscribe(ts); @@ -833,7 +832,7 @@ public Publisher<Integer> call() { ts.assertNotComplete(); // FIXME should this complete the source instead of consuming it? - ps.onComplete(); + pp.onComplete(); ts.assertValues(1, 2, 3, 4, 5); ts.assertComplete(); @@ -842,7 +841,7 @@ public Publisher<Integer> call() { @Test public void testDelaySupplierErrors() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); Flowable<Integer> source = Flowable.range(1, 5); @@ -851,7 +850,7 @@ public void testDelaySupplierErrors() { source.delaySubscription(Flowable.defer(new Callable<Publisher<Integer>>() { @Override public Publisher<Integer> call() { - return ps; + return pp; } })).subscribe(ts); @@ -859,7 +858,7 @@ public Publisher<Integer> call() { ts.assertNoErrors(); ts.assertNotComplete(); - ps.onError(new TestException()); + pp.onError(new TestException()); ts.assertNoValues(); ts.assertNotComplete(); @@ -902,16 +901,16 @@ public void delayWithTimeDelayError() throws Exception { public void testDelaySubscriptionDisposeBeforeTime() { Flowable<Integer> result = Flowable.just(1, 2, 3).delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); result.subscribe(ts); ts.dispose(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -947,15 +946,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.delay(1, TimeUnit.SECONDS); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.delay(1, TimeUnit.SECONDS); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.delay(Functions.justFunction(Flowable.never())); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.delay(Functions.justFunction(Flowable.never())); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java index a3ae8aaf49..723d3b1e59 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -24,87 +23,132 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; +@SuppressWarnings("deprecation") public class FlowableDematerializeTest { + @Test + public void simpleSelector() { + Flowable<Notification<Integer>> notifications = Flowable.just(1, 2).materialize(); + Flowable<Integer> dematerialize = notifications.dematerialize(Functions.<Notification<Integer>>identity()); + + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + + dematerialize.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void selectorCrash() { + Flowable.just(1, 2) + .materialize() + .dematerialize(new Function<Notification<Integer>, Notification<Object>>() { + @Override + public Notification<Object> apply(Notification<Integer> v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Flowable.just(1, 2) + .materialize() + .dematerialize(new Function<Notification<Integer>, Notification<Object>>() { + @Override + public Notification<Object> apply(Notification<Integer> v) throws Exception { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + @Test public void testDematerialize1() { Flowable<Notification<Integer>> notifications = Flowable.just(1, 2).materialize(); Flowable<Integer> dematerialize = notifications.dematerialize(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - dematerialize.subscribe(observer); + dematerialize.subscribe(subscriber); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testDematerialize2() { Throwable exception = new Throwable("test"); - Flowable<Integer> observable = Flowable.error(exception); - Flowable<Integer> dematerialize = observable.materialize().dematerialize(); + Flowable<Integer> flowable = Flowable.error(exception); + Flowable<Integer> dematerialize = flowable.materialize().dematerialize(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - dematerialize.subscribe(observer); + dematerialize.subscribe(subscriber); - verify(observer, times(1)).onError(exception); - verify(observer, times(0)).onComplete(); - verify(observer, times(0)).onNext(any(Integer.class)); + verify(subscriber, times(1)).onError(exception); + verify(subscriber, times(0)).onComplete(); + verify(subscriber, times(0)).onNext(any(Integer.class)); } @Test public void testDematerialize3() { Exception exception = new Exception("test"); - Flowable<Integer> observable = Flowable.error(exception); - Flowable<Integer> dematerialize = observable.materialize().dematerialize(); + Flowable<Integer> flowable = Flowable.error(exception); + Flowable<Integer> dematerialize = flowable.materialize().dematerialize(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - dematerialize.subscribe(observer); + dematerialize.subscribe(subscriber); - verify(observer, times(1)).onError(exception); - verify(observer, times(0)).onComplete(); - verify(observer, times(0)).onNext(any(Integer.class)); + verify(subscriber, times(1)).onError(exception); + verify(subscriber, times(0)).onComplete(); + verify(subscriber, times(0)).onNext(any(Integer.class)); } @Test public void testErrorPassThru() { Exception exception = new Exception("test"); - Flowable<Integer> observable = Flowable.error(exception); - Flowable<Integer> dematerialize = observable.dematerialize(); + Flowable<Integer> flowable = Flowable.error(exception); + Flowable<Integer> dematerialize = flowable.dematerialize(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - dematerialize.subscribe(observer); + dematerialize.subscribe(subscriber); - verify(observer, times(1)).onError(exception); - verify(observer, times(0)).onComplete(); - verify(observer, times(0)).onNext(any(Integer.class)); + verify(subscriber, times(1)).onError(exception); + verify(subscriber, times(0)).onComplete(); + verify(subscriber, times(0)).onNext(any(Integer.class)); } @Test public void testCompletePassThru() { - Flowable<Integer> observable = Flowable.empty(); - Flowable<Integer> dematerialize = observable.dematerialize(); + Flowable<Integer> flowable = Flowable.empty(); + Flowable<Integer> dematerialize = flowable.dematerialize(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(observer); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(subscriber); dematerialize.subscribe(ts); System.out.println(ts.errors()); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(0)).onNext(any(Integer.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(0)).onNext(any(Integer.class)); } @Test @@ -113,13 +157,13 @@ public void testHonorsContractWhenCompleted() { Flowable<Integer> result = source.materialize().dematerialize(); - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -128,13 +172,13 @@ public void testHonorsContractWhenThrows() { Flowable<Integer> result = source.materialize().dematerialize(); - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); - verify(o, never()).onNext(any(Integer.class)); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any(Integer.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } @Test @@ -146,8 +190,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.dematerialize(); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.dematerialize(); } }); } @@ -158,12 +202,12 @@ public void eventsAfterDematerializedTerminal() { try { new Flowable<Object>() { @Override - protected void subscribeActual(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(Notification.createOnComplete()); - observer.onNext(Notification.createOnNext(1)); - observer.onNext(Notification.createOnError(new TestException("First"))); - observer.onError(new TestException("Second")); + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(Notification.createOnComplete()); + subscriber.onNext(Notification.createOnNext(1)); + subscriber.onNext(Notification.createOnError(new TestException("First"))); + subscriber.onError(new TestException("Second")); } } .dematerialize() @@ -176,4 +220,19 @@ protected void subscribeActual(Subscriber<? super Object> observer) { RxJavaPlugins.reset(); } } + + @Test + public void nonNotificationInstanceAfterDispose() { + new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(Notification.createOnComplete()); + subscriber.onNext(1); + } + } + .dematerialize() + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java index d2526adbf2..2b5bcea1e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDetachTest.java @@ -26,7 +26,6 @@ import io.reactivex.functions.Function; import io.reactivex.subscribers.TestSubscriber; - public class FlowableDetachTest { Object o; @@ -87,7 +86,6 @@ public void range() { ts.assertComplete(); } - @Test public void backpressured() throws Exception { o = new Object(); @@ -169,8 +167,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.onTerminateDetach(); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.onTerminateDetach(); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java index 0404682e9d..86b6200440 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -143,29 +142,29 @@ public void error() { @Test public void fusedSync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 1, 2, 1, 3, 2, 4, 5, 4) .distinct() - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedAsync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); us .distinct() - .subscribe(to); + .subscribe(ts); TestHelper.emit(us, 1, 1, 2, 1, 3, 2, 4, 5, 4); - SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); } @@ -175,14 +174,14 @@ public void fusedClear() { .distinct() .subscribe(new FlowableSubscriber<Integer>() { @Override - public void onSubscribe(Subscription d) { - QueueSubscription<?> qd = (QueueSubscription<?>)d; + public void onSubscribe(Subscription s) { + QueueSubscription<?> qs = (QueueSubscription<?>)s; - assertFalse(qd.isEmpty()); + assertFalse(qs.isEmpty()); - qd.clear(); + qs.clear(); - assertTrue(qd.isEmpty()); + assertTrue(qs.isEmpty()); } @Override @@ -232,14 +231,14 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - - observer.onNext(1); - observer.onComplete(); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .distinct() diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java index f63ded514c..0ea9353505 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; @@ -182,9 +181,9 @@ public boolean test(Integer a, Integer b) { return a.equals(b); } }) - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 2, 4, 1, 2); } @@ -203,9 +202,9 @@ public boolean test(Integer v) { return true; } }) - .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueSubscription.ANY, false)) + .to(SubscriberFusion.<Integer>test(Long.MAX_VALUE, QueueFuseable.ANY, false)) .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 2, 4, 1, 2); } @@ -234,7 +233,7 @@ public void accept(Throwable t) { @Test public void customComparator() { - Flowable<String> source = Flowable.just("a", "b", "B", "A","a", "C"); + Flowable<String> source = Flowable.just("a", "b", "B", "A", "a", "C"); TestSubscriber<String> ts = TestSubscriber.create(); @@ -253,7 +252,7 @@ public boolean test(String a, String b) { @Test public void customComparatorThrows() { - Flowable<String> source = Flowable.just("a", "b", "B", "A","a", "C"); + Flowable<String> source = Flowable.just("a", "b", "B", "A", "a", "C"); TestSubscriber<String> ts = TestSubscriber.create(); @@ -272,7 +271,7 @@ public boolean test(String a, String b) { @Test public void fused() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 2, 2, 3, 3, 4, 5) .distinctUntilChanged(new BiPredicate<Integer, Integer>() { @@ -281,17 +280,17 @@ public boolean test(Integer a, Integer b) throws Exception { return a.equals(b); } }) - .subscribe(to); + .subscribe(ts); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.SYNC)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5) ; } @Test public void fusedAsync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -302,12 +301,12 @@ public boolean test(Integer a, Integer b) throws Exception { return a.equals(b); } }) - .subscribe(to); + .subscribe(ts); TestHelper.emit(up, 1, 2, 2, 3, 3, 4, 5); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5) ; } @@ -438,7 +437,7 @@ public boolean test(Integer v) throws Exception { @Test public void conditionalFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 2, 1, 3, 3, 4, 3, 5, 5) .distinctUntilChanged() @@ -450,13 +449,13 @@ public boolean test(Integer v) throws Exception { }) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(2, 4); } @Test public void conditionalAsyncFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); up @@ -469,10 +468,9 @@ public boolean test(Integer v) throws Exception { }) .subscribe(ts); - TestHelper.emit(up, 1, 2, 1, 3, 3, 4, 3, 5, 5); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(2, 4); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNextTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNextTest.java index 78b9f05f7d..338d0b0f50 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNextTest.java @@ -23,7 +23,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Consumer; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.processors.UnicastProcessor; import io.reactivex.subscribers.*; @@ -88,13 +88,13 @@ public void empty() { @Test public void syncFused() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5) .doAfterNext(afterNext) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts0, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -102,13 +102,13 @@ public void syncFused() { @Test public void asyncFusedRejected() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.ASYNC); Flowable.range(1, 5) .doAfterNext(afterNext) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts0, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -116,7 +116,7 @@ public void asyncFusedRejected() { @Test public void asyncFused() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.ASYNC); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -126,7 +126,7 @@ public void asyncFused() { .doAfterNext(afterNext) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts0, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -183,14 +183,14 @@ public void emptyConditional() { @Test public void syncFusedConditional() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts0, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -198,14 +198,14 @@ public void syncFusedConditional() { @Test public void asyncFusedRejectedConditional() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.ASYNC); Flowable.range(1, 5) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts0, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -213,7 +213,7 @@ public void asyncFusedRejectedConditional() { @Test public void asyncFusedConditional() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueFuseable.ASYNC); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -224,7 +224,7 @@ public void asyncFusedConditional() { .filter(Functions.alwaysTrue()) .subscribe(ts0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts0, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterTerminateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterTerminateTest.java index 01704a97b6..c8b327fe04 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterTerminateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoAfterTerminateTest.java @@ -18,6 +18,8 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import java.util.List; + import org.junit.*; import org.mockito.Mockito; import org.reactivestreams.Subscriber; @@ -25,21 +27,22 @@ import io.reactivex.*; import io.reactivex.functions.Action; import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; public class FlowableDoAfterTerminateTest { private Action aAction0; - private Subscriber<String> observer; + private Subscriber<String> subscriber; @Before public void before() { aAction0 = Mockito.mock(Action.class); - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } private void checkActionCalled(Flowable<String> input) { - input.doAfterTerminate(aAction0).subscribe(observer); + input.doAfterTerminate(aAction0).subscribe(subscriber); try { verify(aAction0, times(1)).run(); } catch (Throwable ex) { @@ -82,21 +85,28 @@ public void nullFinallyActionShouldBeCheckedASAP() { @Test public void ifFinallyActionThrowsExceptionShouldNotBeSwallowedAndActionShouldBeCalledOnce() throws Exception { - Action finallyAction = Mockito.mock(Action.class); - doThrow(new IllegalStateException()).when(finallyAction).run(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Action finallyAction = Mockito.mock(Action.class); + doThrow(new IllegalStateException()).when(finallyAction).run(); + + TestSubscriber<String> testSubscriber = new TestSubscriber<String>(); - TestSubscriber<String> testSubscriber = new TestSubscriber<String>(); + Flowable + .just("value") + .doAfterTerminate(finallyAction) + .subscribe(testSubscriber); - Flowable - .just("value") - .doAfterTerminate(finallyAction) - .subscribe(testSubscriber); + testSubscriber.assertValue("value"); - testSubscriber.assertValue("value"); + verify(finallyAction).run(); - verify(finallyAction).run(); - // Actual result: - // Not only IllegalStateException was swallowed - // But finallyAction was called twice! + TestHelper.assertError(errors, 0, IllegalStateException.class); + // Actual result: + // Not only IllegalStateException was swallowed + // But finallyAction was called twice! + } finally { + RxJavaPlugins.reset(); + } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java index 8a775ea310..93dd960cf3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoFinallyTest.java @@ -24,7 +24,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.UnicastProcessor; import io.reactivex.subscribers.*; @@ -97,13 +97,13 @@ public Publisher<Object> apply(Flowable<Object> f) throws Exception { @Test public void syncFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5) .doFinally(this) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -111,13 +111,13 @@ public void syncFused() { @Test public void syncFusedBoundary() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC | QueueSubscription.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC | QueueFuseable.BOUNDARY); Flowable.range(1, 5) .doFinally(this) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -125,7 +125,7 @@ public void syncFusedBoundary() { @Test public void asyncFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); UnicastProcessor<Integer> up = UnicastProcessor.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -134,7 +134,7 @@ public void asyncFused() { .doFinally(this) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -142,7 +142,7 @@ public void asyncFused() { @Test public void asyncFusedBoundary() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ASYNC | QueueSubscription.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); UnicastProcessor<Integer> up = UnicastProcessor.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -151,13 +151,12 @@ public void asyncFusedBoundary() { .doFinally(this) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); } - @Test public void normalJustConditional() { Flowable.just(1) @@ -205,14 +204,14 @@ public void normalTakeConditional() { @Test public void syncFusedConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5) .doFinally(this) .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -220,13 +219,13 @@ public void syncFusedConditional() { @Test public void nonFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5).hide() .doFinally(this) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -234,14 +233,14 @@ public void nonFused() { @Test public void nonFusedConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5).hide() .doFinally(this) .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -249,14 +248,14 @@ public void nonFusedConditional() { @Test public void syncFusedBoundaryConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC | QueueSubscription.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC | QueueFuseable.BOUNDARY); Flowable.range(1, 5) .doFinally(this) .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -264,7 +263,7 @@ public void syncFusedBoundaryConditional() { @Test public void asyncFusedConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); UnicastProcessor<Integer> up = UnicastProcessor.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -274,7 +273,7 @@ public void asyncFusedConditional() { .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -282,7 +281,7 @@ public void asyncFusedConditional() { @Test public void asyncFusedBoundaryConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ASYNC | QueueSubscription.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); UnicastProcessor<Integer> up = UnicastProcessor.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -292,7 +291,7 @@ public void asyncFusedBoundaryConditional() { .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -357,7 +356,7 @@ public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") QueueSubscription<Integer> qs = (QueueSubscription<Integer>)s; - qs.requestFusion(QueueSubscription.ANY); + qs.requestFusion(QueueFuseable.ANY); assertFalse(qs.isEmpty()); @@ -404,7 +403,7 @@ public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") QueueSubscription<Integer> qs = (QueueSubscription<Integer>)s; - qs.requestFusion(QueueSubscription.ANY); + qs.requestFusion(QueueFuseable.ANY); assertFalse(qs.isEmpty()); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java index d55c6be3a9..e61fdc4b38 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -37,35 +36,35 @@ public class FlowableDoOnEachTest { - Subscriber<String> subscribedObserver; - Subscriber<String> sideEffectObserver; + Subscriber<String> subscribedSubscriber; + Subscriber<String> sideEffectSubscriber; @Before public void before() { - subscribedObserver = TestHelper.mockSubscriber(); - sideEffectObserver = TestHelper.mockSubscriber(); + subscribedSubscriber = TestHelper.mockSubscriber(); + sideEffectSubscriber = TestHelper.mockSubscriber(); } @Test public void testDoOnEach() { Flowable<String> base = Flowable.just("a", "b", "c"); - Flowable<String> doOnEach = base.doOnEach(sideEffectObserver); + Flowable<String> doOnEach = base.doOnEach(sideEffectSubscriber); - doOnEach.subscribe(subscribedObserver); + doOnEach.subscribe(subscribedSubscriber); // ensure the leaf observer is still getting called - verify(subscribedObserver, never()).onError(any(Throwable.class)); - verify(subscribedObserver, times(1)).onNext("a"); - verify(subscribedObserver, times(1)).onNext("b"); - verify(subscribedObserver, times(1)).onNext("c"); - verify(subscribedObserver, times(1)).onComplete(); + verify(subscribedSubscriber, never()).onError(any(Throwable.class)); + verify(subscribedSubscriber, times(1)).onNext("a"); + verify(subscribedSubscriber, times(1)).onNext("b"); + verify(subscribedSubscriber, times(1)).onNext("c"); + verify(subscribedSubscriber, times(1)).onComplete(); // ensure our injected observer is getting called - verify(sideEffectObserver, never()).onError(any(Throwable.class)); - verify(sideEffectObserver, times(1)).onNext("a"); - verify(sideEffectObserver, times(1)).onNext("b"); - verify(sideEffectObserver, times(1)).onNext("c"); - verify(sideEffectObserver, times(1)).onComplete(); + verify(sideEffectSubscriber, never()).onError(any(Throwable.class)); + verify(sideEffectSubscriber, times(1)).onNext("a"); + verify(sideEffectSubscriber, times(1)).onNext("b"); + verify(sideEffectSubscriber, times(1)).onNext("c"); + verify(sideEffectSubscriber, times(1)).onComplete(); } @Test @@ -81,20 +80,20 @@ public String apply(String s) { } }); - Flowable<String> doOnEach = errs.doOnEach(sideEffectObserver); + Flowable<String> doOnEach = errs.doOnEach(sideEffectSubscriber); - doOnEach.subscribe(subscribedObserver); - verify(subscribedObserver, times(1)).onNext("one"); - verify(subscribedObserver, never()).onNext("two"); - verify(subscribedObserver, never()).onNext("three"); - verify(subscribedObserver, never()).onComplete(); - verify(subscribedObserver, times(1)).onError(any(Throwable.class)); + doOnEach.subscribe(subscribedSubscriber); + verify(subscribedSubscriber, times(1)).onNext("one"); + verify(subscribedSubscriber, never()).onNext("two"); + verify(subscribedSubscriber, never()).onNext("three"); + verify(subscribedSubscriber, never()).onComplete(); + verify(subscribedSubscriber, times(1)).onError(any(Throwable.class)); - verify(sideEffectObserver, times(1)).onNext("one"); - verify(sideEffectObserver, never()).onNext("two"); - verify(sideEffectObserver, never()).onNext("three"); - verify(sideEffectObserver, never()).onComplete(); - verify(sideEffectObserver, times(1)).onError(any(Throwable.class)); + verify(sideEffectSubscriber, times(1)).onNext("one"); + verify(sideEffectSubscriber, never()).onNext("two"); + verify(sideEffectSubscriber, never()).onNext("three"); + verify(sideEffectSubscriber, never()).onComplete(); + verify(sideEffectSubscriber, times(1)).onError(any(Throwable.class)); } @Test @@ -109,12 +108,12 @@ public void accept(String s) { } }); - doOnEach.subscribe(subscribedObserver); - verify(subscribedObserver, times(1)).onNext("one"); - verify(subscribedObserver, times(1)).onNext("two"); - verify(subscribedObserver, never()).onNext("three"); - verify(subscribedObserver, never()).onComplete(); - verify(subscribedObserver, times(1)).onError(any(Throwable.class)); + doOnEach.subscribe(subscribedSubscriber); + verify(subscribedSubscriber, times(1)).onNext("one"); + verify(subscribedSubscriber, times(1)).onNext("two"); + verify(subscribedSubscriber, never()).onNext("three"); + verify(subscribedSubscriber, never()).onComplete(); + verify(subscribedSubscriber, times(1)).onError(any(Throwable.class)); } @@ -180,7 +179,7 @@ public void testFatalError() { // public Flowable<?> apply(Integer integer) { // return Flowable.create(new Publisher<Object>() { // @Override -// public void subscribe(Subscriber<Object> o) { +// public void subscribe(Subscriber<Object> subscriber) { // throw new NullPointerException("Test NPE"); // } // }); @@ -504,7 +503,7 @@ public void accept(Throwable e) throws Exception { @Test public void fused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -524,7 +523,7 @@ public void run() throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -533,7 +532,7 @@ public void run() throws Exception { @Test public void fusedOnErrorCrash() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0 }; @@ -553,7 +552,7 @@ public void run() throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertFailure(TestException.class); assertEquals(0, call[0]); @@ -561,7 +560,7 @@ public void run() throws Exception { @Test public void fusedConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -582,7 +581,7 @@ public void run() throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -591,7 +590,7 @@ public void run() throws Exception { @Test public void fusedOnErrorCrashConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0 }; @@ -612,7 +611,7 @@ public void run() throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertFailure(TestException.class); assertEquals(0, call[0]); @@ -620,7 +619,7 @@ public void run() throws Exception { @Test public void fusedAsync() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -644,7 +643,7 @@ public void run() throws Exception { TestHelper.emit(up, 1, 2, 3, 4, 5); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -653,7 +652,7 @@ public void run() throws Exception { @Test public void fusedAsyncConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -678,7 +677,7 @@ public void run() throws Exception { TestHelper.emit(up, 1, 2, 3, 4, 5); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -687,7 +686,7 @@ public void run() throws Exception { @Test public void fusedAsyncConditional2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -712,7 +711,7 @@ public void run() throws Exception { TestHelper.emit(up, 1, 2, 3, 4, 5); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -728,15 +727,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.doOnEach(new TestSubscriber<Object>()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.doOnEach(new TestSubscriber<Object>()); } }); } @Test public void doOnNextDoOnErrorFused() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { @@ -751,8 +750,8 @@ public void accept(Throwable e) throws Exception { }) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); @@ -762,7 +761,7 @@ public void accept(Throwable e) throws Exception { @Test public void doOnNextDoOnErrorCombinedFused() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .compose(new FlowableTransformer<Integer, Integer>() { @Override public Publisher<Integer> apply(Flowable<Integer> v) { @@ -787,8 +786,8 @@ public void accept(Throwable e) throws Exception { }) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); @@ -798,7 +797,7 @@ public void accept(Throwable e) throws Exception { @Test public void doOnNextDoOnErrorFused2() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { @@ -819,8 +818,8 @@ public void accept(Throwable e) throws Exception { }) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); @@ -831,7 +830,7 @@ public void accept(Throwable e) throws Exception { @Test public void doOnNextDoOnErrorFusedConditional() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { @@ -847,8 +846,8 @@ public void accept(Throwable e) throws Exception { .filter(Functions.alwaysTrue()) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); @@ -858,7 +857,7 @@ public void accept(Throwable e) throws Exception { @Test public void doOnNextDoOnErrorFusedConditional2() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { @@ -880,8 +879,8 @@ public void accept(Throwable e) throws Exception { .filter(Functions.alwaysTrue()) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); @@ -892,7 +891,7 @@ public void accept(Throwable e) throws Exception { @Test public void doOnNextDoOnErrorCombinedFusedConditional() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .compose(new FlowableTransformer<Integer, Integer>() { @Override public Publisher<Integer> apply(Flowable<Integer> v) { @@ -918,8 +917,8 @@ public void accept(Throwable e) throws Exception { .filter(Functions.alwaysTrue()) .publish(); - TestSubscriber<Integer> ts = co.test(); - co.connect(); + TestSubscriber<Integer> ts = cf.test(); + cf.connect(); ts.assertFailure(CompositeException.class); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java index af309b0e9b..34578bbfee 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycleTest.java @@ -48,8 +48,8 @@ public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { @Override - public Publisher<Object> apply(Flowable<Object> o) throws Exception { - return o + public Publisher<Object> apply(Flowable<Object> f) throws Exception { + return f .doOnLifecycle(new Consumer<Subscription>() { @Override public void accept(Subscription s) throws Exception { @@ -87,7 +87,7 @@ public void run() throws Exception { ); assertEquals(1, calls[0]); - assertEquals(2, calls[1]); + assertEquals(1, calls[1]); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java index 1d30f4663c..4502206206 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnRequestTest.java @@ -83,7 +83,7 @@ public void onNext(Integer t) { request(t); } }); - assertEquals(Arrays.asList(3L,1L,2L,3L,4L,5L), requests); + assertEquals(Arrays.asList(3L, 1L, 2L, 3L, 4L, 5L), requests); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnSubscribeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnSubscribeTest.java index 339125c7e8..0b75ec3338 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnSubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnSubscribeTest.java @@ -29,23 +29,23 @@ public class FlowableDoOnSubscribeTest { @Test public void testDoOnSubscribe() throws Exception { final AtomicInteger count = new AtomicInteger(); - Flowable<Integer> o = Flowable.just(1).doOnSubscribe(new Consumer<Subscription>() { + Flowable<Integer> f = Flowable.just(1).doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) { count.incrementAndGet(); } }); - o.subscribe(); - o.subscribe(); - o.subscribe(); + f.subscribe(); + f.subscribe(); + f.subscribe(); assertEquals(3, count.get()); } @Test public void testDoOnSubscribe2() throws Exception { final AtomicInteger count = new AtomicInteger(); - Flowable<Integer> o = Flowable.just(1).doOnSubscribe(new Consumer<Subscription>() { + Flowable<Integer> f = Flowable.just(1).doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) { count.incrementAndGet(); @@ -57,7 +57,7 @@ public void accept(Subscription s) { } }); - o.subscribe(); + f.subscribe(); assertEquals(2, count.get()); } @@ -67,7 +67,7 @@ public void testDoOnUnSubscribeWorksWithRefCount() throws Exception { final AtomicInteger countBefore = new AtomicInteger(); final AtomicInteger countAfter = new AtomicInteger(); final AtomicReference<Subscriber<? super Integer>> sref = new AtomicReference<Subscriber<? super Integer>>(); - Flowable<Integer> o = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { @@ -89,16 +89,16 @@ public void accept(Subscription s) { } }); - o.subscribe(); - o.subscribe(); - o.subscribe(); + f.subscribe(); + f.subscribe(); + f.subscribe(); assertEquals(1, countBefore.get()); assertEquals(1, onSubscribed.get()); assertEquals(3, countAfter.get()); sref.get().onComplete(); - o.subscribe(); - o.subscribe(); - o.subscribe(); + f.subscribe(); + f.subscribe(); + f.subscribe(); assertEquals(2, countBefore.get()); assertEquals(2, onSubscribed.get()); assertEquals(6, countAfter.get()); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java index 2987102666..de1bd0d57a 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnUnsubscribeTest.java @@ -24,6 +24,7 @@ import io.reactivex.Flowable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.*; +import io.reactivex.processors.BehaviorProcessor; import io.reactivex.subscribers.TestSubscriber; public class FlowableDoOnUnsubscribeTest { @@ -148,4 +149,24 @@ public void run() { assertEquals("There should exactly 1 un-subscription events for upper stream", 1, upperCount.get()); assertEquals("There should exactly 1 un-subscription events for lower stream", 1, lowerCount.get()); } + + @Test + public void noReentrantDispose() { + + final AtomicInteger cancelCalled = new AtomicInteger(); + + final BehaviorProcessor<Integer> p = BehaviorProcessor.create(); + p.doOnCancel(new Action() { + @Override + public void run() throws Exception { + cancelCalled.incrementAndGet(); + p.onNext(2); + } + }) + .firstOrError() + .subscribe() + .dispose(); + + assertEquals(1, cancelCalled.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java index 1b795665ad..d5665d6bf3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableElementAtTest.java @@ -186,27 +186,26 @@ public void elementAtOrErrorIndex1OnEmptySource() { .assertFailure(NoSuchElementException.class); } - @Test public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { @Override - public Publisher<Object> apply(Flowable<Object> o) throws Exception { - return o.elementAt(0).toFlowable(); + public Publisher<Object> apply(Flowable<Object> f) throws Exception { + return f.elementAt(0).toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToMaybe(new Function<Flowable<Object>, Maybe<Object>>() { @Override - public Maybe<Object> apply(Flowable<Object> o) throws Exception { - return o.elementAt(0); + public Maybe<Object> apply(Flowable<Object> f) throws Exception { + return f.elementAt(0); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, Single<Object>>() { @Override - public Single<Object> apply(Flowable<Object> o) throws Exception { - return o.elementAt(0, 1); + public Single<Object> apply(Flowable<Object> f) throws Exception { + return f.elementAt(0, 1); } }); } @@ -229,7 +228,6 @@ public void errorFlowable() { .assertFailure(TestException.class); } - @Test public void error() { Flowable.error(new TestException()) @@ -338,13 +336,13 @@ public void badSource2() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .elementAt(0, 1) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java index eaa2d87515..a6928a1157 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -39,7 +38,7 @@ public class FlowableFilterTest { @Test public void testFilter() { Flowable<String> w = Flowable.just("one", "two", "three"); - Flowable<String> Flowable = w.filter(new Predicate<String>() { + Flowable<String> flowable = w.filter(new Predicate<String>() { @Override public boolean test(String t1) { @@ -47,15 +46,15 @@ public boolean test(String t1) { } }); - Subscriber<String> Subscriber = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.subscribe(Subscriber); + flowable.subscribe(subscriber); - verify(Subscriber, Mockito.never()).onNext("one"); - verify(Subscriber, times(1)).onNext("two"); - verify(Subscriber, Mockito.never()).onNext("three"); - verify(Subscriber, Mockito.never()).onError(any(Throwable.class)); - verify(Subscriber, times(1)).onComplete(); + verify(subscriber, Mockito.never()).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } /** @@ -66,7 +65,7 @@ public boolean test(String t1) { @Test(timeout = 500) public void testWithBackpressure() throws InterruptedException { Flowable<String> w = Flowable.just("one", "two", "three"); - Flowable<String> o = w.filter(new Predicate<String>() { + Flowable<String> f = w.filter(new Predicate<String>() { @Override public boolean test(String t1) { @@ -100,7 +99,7 @@ public void onNext(String t) { // this means it will only request "one" and "two", expecting to receive them before requesting more ts.request(2); - o.subscribe(ts); + f.subscribe(ts); // this will wait forever unless OperatorTake handles the request(n) on filtered items latch.await(); @@ -113,7 +112,7 @@ public void onNext(String t) { @Test(timeout = 500000) public void testWithBackpressure2() throws InterruptedException { Flowable<Integer> w = Flowable.range(1, Flowable.bufferSize() * 2); - Flowable<Integer> o = w.filter(new Predicate<Integer>() { + Flowable<Integer> f = w.filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -146,7 +145,7 @@ public void onNext(Integer t) { // this means it will only request 1 item and expect to receive more ts.request(1); - o.subscribe(ts); + f.subscribe(ts); // this will wait forever unless OperatorTake handles the request(n) on filtered items latch.await(); @@ -181,22 +180,22 @@ public void testFatalError() { @Test public void functionCrashUnsubscribes() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - ps.filter(new Predicate<Integer>() { + pp.filter(new Predicate<Integer>() { @Override public boolean test(Integer v) { throw new TestException(); } }).subscribe(ts); - Assert.assertTrue("Not subscribed?", ps.hasSubscribers()); + Assert.assertTrue("Not subscribed?", pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - Assert.assertFalse("Subscribed?", ps.hasSubscribers()); + Assert.assertFalse("Subscribed?", pp.hasSubscribers()); ts.assertError(TestException.class); } @@ -245,7 +244,7 @@ public void conditionalNone2() { @Test public void conditionalFusedSync() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(Functions.alwaysTrue()) @@ -253,13 +252,13 @@ public void conditionalFusedSync() { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); } @Test public void conditionalFusedSync2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(Functions.alwaysFalse()) @@ -267,13 +266,13 @@ public void conditionalFusedSync2() { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(); } @Test public void conditionalFusedAsync() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -290,13 +289,13 @@ public void conditionalFusedAsync() { up.onComplete(); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5); } @Test public void conditionalFusedNoneAsync() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -313,13 +312,13 @@ public void conditionalFusedNoneAsync() { up.onComplete(); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @Test public void conditionalFusedNoneAsync2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -336,7 +335,7 @@ public void conditionalFusedNoneAsync2() { up.onComplete(); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -415,33 +414,33 @@ public boolean test(Integer v) throws Exception { @Test public void syncFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(Functions.alwaysTrue()) .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); } @Test public void syncNoneFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(Functions.alwaysFalse()) .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(); } @Test public void syncNoneFused2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(Functions.alwaysFalse()) @@ -449,7 +448,7 @@ public void syncNoneFused2() { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(); } @@ -555,15 +554,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.filter(Functions.alwaysTrue()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.filter(Functions.alwaysTrue()); } }); } @Test public void fusedSync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .filter(new Predicate<Integer>() { @@ -572,15 +571,15 @@ public boolean test(Integer v) throws Exception { return v % 2 == 0; } }) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(2, 4); } @Test public void fusedAsync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); @@ -591,17 +590,17 @@ public boolean test(Integer v) throws Exception { return v % 2 == 0; } }) - .subscribe(to); + .subscribe(ts); TestHelper.emit(us, 1, 2, 3, 4, 5); - SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(2, 4); } @Test public void fusedReject() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY | QueueFuseable.BOUNDARY); Flowable.range(1, 5) .filter(new Predicate<Integer>() { @@ -610,9 +609,9 @@ public boolean test(Integer v) throws Exception { return v % 2 == 0; } }) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(2, 4); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFirstTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFirstTest.java index 38c5723726..3d4eacd281 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFirstTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFirstTest.java @@ -92,46 +92,46 @@ public void testFirstOrElseWithPredicateOfSomeFlowable() { @Test public void testFirstFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3).firstElement().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1, 2, 3).firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstWithOneElementFlowable() { - Flowable<Integer> observable = Flowable.just(1).firstElement().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1).firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstWithEmptyFlowable() { - Flowable<Integer> observable = Flowable.<Integer> empty().firstElement().toFlowable(); + Flowable<Integer> flowable = Flowable.<Integer> empty().firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstWithPredicateFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -140,18 +140,18 @@ public boolean test(Integer t1) { }) .firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstWithPredicateAndOneElementFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2) + Flowable<Integer> flowable = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -160,18 +160,18 @@ public boolean test(Integer t1) { }) .firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstWithPredicateAndEmptyFlowable() { - Flowable<Integer> observable = Flowable.just(1) + Flowable<Integer> flowable = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -180,59 +180,59 @@ public boolean test(Integer t1) { }) .firstElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3) + Flowable<Integer> flowable = Flowable.just(1, 2, 3) .first(4).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultWithOneElementFlowable() { - Flowable<Integer> observable = Flowable.just(1).first(2).toFlowable(); + Flowable<Integer> flowable = Flowable.just(1).first(2).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultWithEmptyFlowable() { - Flowable<Integer> observable = Flowable.<Integer> empty() + Flowable<Integer> flowable = Flowable.<Integer> empty() .first(1).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultWithPredicateFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -241,18 +241,18 @@ public boolean test(Integer t1) { }) .first(8).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultWithPredicateAndOneElementFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2) + Flowable<Integer> flowable = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -261,18 +261,18 @@ public boolean test(Integer t1) { }) .first(4).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testFirstOrDefaultWithPredicateAndEmptyFlowable() { - Flowable<Integer> observable = Flowable.just(1) + Flowable<Integer> flowable = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -281,12 +281,12 @@ public boolean test(Integer t1) { }) .first(2).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -332,9 +332,9 @@ public void testFirstOrElseWithPredicateOfSome() { @Test public void testFirst() { - Maybe<Integer> observable = Flowable.just(1, 2, 3).firstElement(); + Maybe<Integer> maybe = Flowable.just(1, 2, 3).firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm, times(1)).onSuccess(1); @@ -343,9 +343,9 @@ public void testFirst() { @Test public void testFirstWithOneElement() { - Maybe<Integer> observable = Flowable.just(1).firstElement(); + Maybe<Integer> maybe = Flowable.just(1).firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm, times(1)).onSuccess(1); @@ -354,9 +354,9 @@ public void testFirstWithOneElement() { @Test public void testFirstWithEmpty() { - Maybe<Integer> observable = Flowable.<Integer> empty().firstElement(); + Maybe<Integer> maybe = Flowable.<Integer> empty().firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm).onComplete(); @@ -366,7 +366,7 @@ public void testFirstWithEmpty() { @Test public void testFirstWithPredicate() { - Maybe<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Maybe<Integer> maybe = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -375,7 +375,7 @@ public boolean test(Integer t1) { }) .firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm, times(1)).onSuccess(2); @@ -384,7 +384,7 @@ public boolean test(Integer t1) { @Test public void testFirstWithPredicateAndOneElement() { - Maybe<Integer> observable = Flowable.just(1, 2) + Maybe<Integer> maybe = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -393,7 +393,7 @@ public boolean test(Integer t1) { }) .firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm, times(1)).onSuccess(2); @@ -402,7 +402,7 @@ public boolean test(Integer t1) { @Test public void testFirstWithPredicateAndEmpty() { - Maybe<Integer> observable = Flowable.just(1) + Maybe<Integer> maybe = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -411,7 +411,7 @@ public boolean test(Integer t1) { }) .firstElement(); - observable.subscribe(wm); + maybe.subscribe(wm); InOrder inOrder = inOrder(wm); inOrder.verify(wm).onComplete(); @@ -421,10 +421,10 @@ public boolean test(Integer t1) { @Test public void testFirstOrDefault() { - Single<Integer> observable = Flowable.just(1, 2, 3) + Single<Integer> single = Flowable.just(1, 2, 3) .first(4); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(1); @@ -433,9 +433,9 @@ public void testFirstOrDefault() { @Test public void testFirstOrDefaultWithOneElement() { - Single<Integer> observable = Flowable.just(1).first(2); + Single<Integer> single = Flowable.just(1).first(2); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(1); @@ -444,10 +444,10 @@ public void testFirstOrDefaultWithOneElement() { @Test public void testFirstOrDefaultWithEmpty() { - Single<Integer> observable = Flowable.<Integer> empty() + Single<Integer> single = Flowable.<Integer> empty() .first(1); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(1); @@ -456,7 +456,7 @@ public void testFirstOrDefaultWithEmpty() { @Test public void testFirstOrDefaultWithPredicate() { - Single<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Single<Integer> single = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -465,7 +465,7 @@ public boolean test(Integer t1) { }) .first(8); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(2); @@ -474,7 +474,7 @@ public boolean test(Integer t1) { @Test public void testFirstOrDefaultWithPredicateAndOneElement() { - Single<Integer> observable = Flowable.just(1, 2) + Single<Integer> single = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -483,7 +483,7 @@ public boolean test(Integer t1) { }) .first(4); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(2); @@ -492,7 +492,7 @@ public boolean test(Integer t1) { @Test public void testFirstOrDefaultWithPredicateAndEmpty() { - Single<Integer> observable = Flowable.just(1) + Single<Integer> single = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -501,7 +501,7 @@ public boolean test(Integer t1) { }) .first(2); - observable.subscribe(wo); + single.subscribe(wo); InOrder inOrder = inOrder(wo); inOrder.verify(wo, times(1)).onSuccess(2); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java index c91e7b8ef9..62cee79161 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableTest.java @@ -25,6 +25,7 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.processors.PublishProcessor; @@ -48,9 +49,9 @@ public CompletableSource apply(Integer v) throws Exception { @Test public void mapperThrowsFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -59,20 +60,20 @@ public CompletableSource apply(Integer v) throws Exception { }).<Integer>toFlowable() .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void mapperReturnsNullFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -81,13 +82,13 @@ public CompletableSource apply(Integer v) throws Exception { }).<Integer>toFlowable() .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(NullPointerException.class); + ts.assertFailure(NullPointerException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test @@ -133,7 +134,7 @@ public CompletableSource apply(Integer v) throws Exception { @Test public void normalDelayErrorAllFlowable() { - TestSubscriber<Integer> to = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) + TestSubscriber<Integer> ts = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -143,7 +144,7 @@ public CompletableSource apply(Integer v) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); for (int i = 0; i < 11; i++) { TestHelper.assertError(errors, i, TestException.class); @@ -152,7 +153,7 @@ public CompletableSource apply(Integer v) throws Exception { @Test public void normalDelayInnerErrorAllFlowable() { - TestSubscriber<Integer> to = Flowable.range(1, 10) + TestSubscriber<Integer> ts = Flowable.range(1, 10) .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -162,7 +163,7 @@ public CompletableSource apply(Integer v) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); for (int i = 0; i < 10; i++) { TestHelper.assertError(errors, i, TestException.class); @@ -182,10 +183,9 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fusedFlowable() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 10) .flatMapCompletable(new Function<Integer, CompletableSource>() { @@ -194,11 +194,11 @@ public CompletableSource apply(Integer v) throws Exception { return Completable.complete(); } }).<Integer>toFlowable() - .subscribe(to); + .subscribe(ts); - to + ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -217,9 +217,9 @@ public CompletableSource apply(Integer v) throws Exception { @Test public void mapperThrows() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Void> to = ps + TestObserver<Void> to = pp .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -228,20 +228,20 @@ public CompletableSource apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); to.assertFailure(TestException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void mapperReturnsNull() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Void> to = ps + TestObserver<Void> to = pp .flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { @@ -250,13 +250,13 @@ public CompletableSource apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); to.assertFailure(NullPointerException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test @@ -337,10 +337,9 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 10) .flatMapCompletable(new Function<Integer, CompletableSource>() { @@ -354,7 +353,7 @@ public CompletableSource apply(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -398,8 +397,8 @@ public CompletableSource apply(Integer v) throws Exception { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.flatMapCompletable(new Function<Integer, CompletableSource>() { + public Object apply(Flowable<Integer> f) throws Exception { + return f.flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { return Completable.complete(); @@ -421,15 +420,15 @@ public CompletableSource apply(Integer v) throws Exception { .toFlowable() .subscribe(new FlowableSubscriber<Object>() { @Override - public void onSubscribe(Subscription d) { - QueueSubscription<?> qd = (QueueSubscription<?>)d; + public void onSubscribe(Subscription s) { + QueueSubscription<?> qs = (QueueSubscription<?>)s; try { - assertNull(qd.poll()); + assertNull(qs.poll()); } catch (Throwable ex) { throw new RuntimeException(ex); } - assertTrue(qd.isEmpty()); - qd.clear(); + assertTrue(qs.isEmpty()); + qs.clear(); } @Override @@ -454,14 +453,14 @@ public void innerObserverFlowable() { public CompletableSource apply(Integer v) throws Exception { return new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); - assertFalse(((Disposable)s).isDisposed()); + assertFalse(((Disposable)observer).isDisposed()); - ((Disposable)s).dispose(); + ((Disposable)observer).dispose(); - assertTrue(((Disposable)s).isDisposed()); + assertTrue(((Disposable)observer).isDisposed()); } }; } @@ -474,8 +473,8 @@ protected void subscribeActual(CompletableObserver s) { public void badSourceFlowable() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.flatMapCompletable(new Function<Integer, CompletableSource>() { + public Object apply(Flowable<Integer> f) throws Exception { + return f.flatMapCompletable(new Function<Integer, CompletableSource>() { @Override public CompletableSource apply(Integer v) throws Exception { return Completable.complete(); @@ -493,14 +492,14 @@ public void innerObserver() { public CompletableSource apply(Integer v) throws Exception { return new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); - assertFalse(((Disposable)s).isDisposed()); + assertFalse(((Disposable)observer).isDisposed()); - ((Disposable)s).dispose(); + ((Disposable)observer).dispose(); - assertTrue(((Disposable)s).isDisposed()); + assertTrue(((Disposable)observer).isDisposed()); } }; } @@ -523,4 +522,21 @@ public CompletableSource apply(Integer v) throws Exception { .test() .assertFailure(TestException.class); } + + @Test + public void asyncMaxConcurrency() { + for (int itemCount = 1; itemCount <= 100000; itemCount *= 10) { + for (int concurrency = 1; concurrency <= 256; concurrency *= 2) { + Flowable.range(1, itemCount) + .flatMapCompletable( + Functions.justFunction(Completable.complete() + .subscribeOn(Schedulers.computation())) + , false, concurrency) + .test() + .withTag("itemCount=" + itemCount + ", concurrency=" + concurrency) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java index 4f5a594abd..aab0940c46 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybeTest.java @@ -123,9 +123,9 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { @Test public void mapperThrowsFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(Integer v) throws Exception { @@ -134,20 +134,20 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void mapperReturnsNullFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(Integer v) throws Exception { @@ -156,18 +156,18 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(NullPointerException.class); + ts.assertFailure(NullPointerException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void normalDelayErrorAll() { - TestSubscriber<Integer> to = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) + TestSubscriber<Integer> ts = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) .flatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(Integer v) throws Exception { @@ -177,7 +177,7 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); for (int i = 0; i < 11; i++) { TestHelper.assertError(errors, i, TestException.class); @@ -261,7 +261,8 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { @Test public void middleError() { - Flowable.fromArray(new String[]{"1","a","2"}).flatMapMaybe(new Function<String, MaybeSource<Integer>>() { + Flowable.fromArray(new String[]{"1", "a", "2"}) + .flatMapMaybe(new Function<String, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(final String s) throws NumberFormatException { //return Maybe.just(Integer.valueOf(s)); //This works @@ -352,46 +353,46 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { @Test public void successError() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.range(1, 2) + TestSubscriber<Integer> ts = Flowable.range(1, 2) .flatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(Integer v) throws Exception { if (v == 2) { - return ps.singleElement(); + return pp.singleElement(); } return Maybe.error(new TestException()); } }, true, Integer.MAX_VALUE) .test(); - ps.onNext(1); - ps.onComplete(); + pp.onNext(1); + pp.onComplete(); - to + ts .assertFailure(TestException.class, 1); } @Test public void completeError() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.range(1, 2) + TestSubscriber<Integer> ts = Flowable.range(1, 2) .flatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(Integer v) throws Exception { if (v == 2) { - return ps.singleElement(); + return pp.singleElement(); } return Maybe.error(new TestException()); } }, true, Integer.MAX_VALUE) .test(); - ps.onComplete(); + pp.onComplete(); - to + ts .assertFailure(TestException.class); } @@ -411,10 +412,10 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onError(new TestException("Second")); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onError(new TestException("Second")); } } .flatMapMaybe(Functions.justFunction(Maybe.just(2))) @@ -451,72 +452,72 @@ protected void subscribeActual(MaybeObserver<? super Integer> observer) { @Test public void emissionQueueTrigger() { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); } } }; - Flowable.just(ps1, ps2) + Flowable.just(pp1, pp2) .flatMapMaybe(new Function<PublishProcessor<Integer>, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(PublishProcessor<Integer> v) throws Exception { return v.singleElement(); } }) - .subscribe(to); + .subscribe(ts); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void emissionQueueTrigger2() { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); - final PublishProcessor<Integer> ps3 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp3 = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); } } }; - Flowable.just(ps1, ps2, ps3) + Flowable.just(pp1, pp2, pp3) .flatMapMaybe(new Function<PublishProcessor<Integer>, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(PublishProcessor<Integer> v) throws Exception { return v.singleElement(); } }) - .subscribe(to); + .subscribe(ts); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - ps3.onComplete(); + pp3.onComplete(); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void disposeInner() { - final TestSubscriber<Object> to = new TestSubscriber<Object>(); + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); Flowable.just(1).flatMapMaybe(new Function<Integer, MaybeSource<Object>>() { @Override @@ -528,30 +529,30 @@ protected void subscribeActual(MaybeObserver<? super Object> observer) { assertFalse(((Disposable)observer).isDisposed()); - to.dispose(); + ts.dispose(); assertTrue(((Disposable)observer).isDisposed()); } }; } }) - .subscribe(to); + .subscribe(ts); - to + ts .assertEmpty(); } @Test public void innerSuccessCompletesAfterMain() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.just(1).flatMapMaybe(Functions.justFunction(ps.singleElement())) + TestSubscriber<Integer> ts = Flowable.just(1).flatMapMaybe(Functions.justFunction(pp.singleElement())) .test(); - ps.onNext(2); - ps.onComplete(); + pp.onNext(2); + pp.onComplete(); - to + ts .assertResult(2); } @@ -584,20 +585,20 @@ public void errorDelayed() { @Test public void requestCancelRace() { - for (int i = 0; i < 500; i++) { - final TestSubscriber<Integer> to = Flowable.just(1).concatWith(Flowable.<Integer>never()) + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestSubscriber<Integer> ts = Flowable.just(1).concatWith(Flowable.<Integer>never()) .flatMapMaybe(Functions.justFunction(Maybe.just(2))).test(0); Runnable r1 = new Runnable() { @Override public void run() { - to.request(1); + ts.request(1); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java index 0e39156baa..ac137d6c3d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingleTest.java @@ -110,9 +110,9 @@ public SingleSource<Integer> apply(Integer v) throws Exception { @Test public void mapperThrowsFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapSingle(new Function<Integer, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Integer v) throws Exception { @@ -121,20 +121,20 @@ public SingleSource<Integer> apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void mapperReturnsNullFlowable() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .flatMapSingle(new Function<Integer, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Integer v) throws Exception { @@ -143,18 +143,18 @@ public SingleSource<Integer> apply(Integer v) throws Exception { }) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(NullPointerException.class); + ts.assertFailure(NullPointerException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void normalDelayErrorAll() { - TestSubscriber<Integer> to = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) + TestSubscriber<Integer> ts = Flowable.range(1, 10).concatWith(Flowable.<Integer>error(new TestException())) .flatMapSingle(new Function<Integer, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Integer v) throws Exception { @@ -164,7 +164,7 @@ public SingleSource<Integer> apply(Integer v) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); for (int i = 0; i < 11; i++) { TestHelper.assertError(errors, i, TestException.class); @@ -248,7 +248,8 @@ public SingleSource<Integer> apply(Integer v) throws Exception { @Test public void middleError() { - Flowable.fromArray(new String[]{"1","a","2"}).flatMapSingle(new Function<String, SingleSource<Integer>>() { + Flowable.fromArray(new String[]{"1", "a", "2"}) + .flatMapSingle(new Function<String, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works @@ -284,24 +285,24 @@ public SingleSource<Integer> apply(Integer v) throws Exception { @Test public void successError() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.range(1, 2) + TestSubscriber<Integer> ts = Flowable.range(1, 2) .flatMapSingle(new Function<Integer, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Integer v) throws Exception { if (v == 2) { - return ps.singleOrError(); + return pp.singleOrError(); } return Single.error(new TestException()); } }, true, Integer.MAX_VALUE) .test(); - ps.onNext(1); - ps.onComplete(); + pp.onNext(1); + pp.onComplete(); - to + ts .assertFailure(TestException.class, 1); } @@ -331,10 +332,10 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onError(new TestException("Second")); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onError(new TestException("Second")); } } .flatMapSingle(Functions.justFunction(Single.just(2))) @@ -371,38 +372,38 @@ protected void subscribeActual(SingleObserver<? super Integer> observer) { @Test public void emissionQueueTrigger() { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); } } }; - Flowable.just(ps1, ps2) + Flowable.just(pp1, pp2) .flatMapSingle(new Function<PublishProcessor<Integer>, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(PublishProcessor<Integer> v) throws Exception { return v.singleOrError(); } }) - .subscribe(to); + .subscribe(ts); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void disposeInner() { - final TestSubscriber<Object> to = new TestSubscriber<Object>(); + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); Flowable.just(1).flatMapSingle(new Function<Integer, SingleSource<Object>>() { @Override @@ -414,30 +415,30 @@ protected void subscribeActual(SingleObserver<? super Object> observer) { assertFalse(((Disposable)observer).isDisposed()); - to.dispose(); + ts.dispose(); assertTrue(((Disposable)observer).isDisposed()); } }; } }) - .subscribe(to); + .subscribe(ts); - to + ts .assertEmpty(); } @Test public void innerSuccessCompletesAfterMain() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = Flowable.just(1).flatMapSingle(Functions.justFunction(ps.singleOrError())) + TestSubscriber<Integer> ts = Flowable.just(1).flatMapSingle(Functions.justFunction(pp.singleOrError())) .test(); - ps.onNext(2); - ps.onComplete(); + pp.onNext(2); + pp.onComplete(); - to + ts .assertResult(2); } @@ -470,20 +471,20 @@ public void errorDelayed() { @Test public void requestCancelRace() { - for (int i = 0; i < 500; i++) { - final TestSubscriber<Integer> to = Flowable.just(1).concatWith(Flowable.<Integer>never()) + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestSubscriber<Integer> ts = Flowable.just(1).concatWith(Flowable.<Integer>never()) .flatMapSingle(Functions.justFunction(Single.just(2))).test(0); Runnable r1 = new Runnable() { @Override public void run() { - to.request(1); + ts.request(1); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java index 6a509ad802..e232af7bd4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -36,7 +35,7 @@ public class FlowableFlatMapTest { @Test public void testNormal() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Integer> list = Arrays.asList(1, 2, 3); @@ -56,20 +55,20 @@ public Integer apply(Integer t1, Integer t2) { List<Integer> source = Arrays.asList(16, 32, 64); - Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(o); + Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(subscriber); for (Integer s : source) { for (Integer v : list) { - verify(o).onNext(s | v); + verify(subscriber).onNext(s | v); } } - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testCollectionFunctionThrows() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Function<Integer, List<Integer>> func = new Function<Integer, List<Integer>>() { @Override @@ -87,16 +86,16 @@ public Integer apply(Integer t1, Integer t2) { List<Integer> source = Arrays.asList(16, 32, 64); - Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(o); + Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(subscriber); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber).onError(any(TestException.class)); } @Test public void testResultFunctionThrows() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Integer> list = Arrays.asList(1, 2, 3); @@ -116,16 +115,16 @@ public Integer apply(Integer t1, Integer t2) { List<Integer> source = Arrays.asList(16, 32, 64); - Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(o); + Flowable.fromIterable(source).flatMapIterable(func, resFunc).subscribe(subscriber); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber).onError(any(TestException.class)); } @Test public void testMergeError() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Function<Integer, Flowable<Integer>> func = new Function<Integer, Flowable<Integer>>() { @Override @@ -143,11 +142,11 @@ public Integer apply(Integer t1, Integer t2) { List<Integer> source = Arrays.asList(16, 32, 64); - Flowable.fromIterable(source).flatMap(func, resFunc).subscribe(o); + Flowable.fromIterable(source).flatMap(func, resFunc).subscribe(subscriber); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber).onError(any(TestException.class)); } <T, R> Function<T, R> just(final R value) { @@ -178,18 +177,18 @@ public void testFlatMapTransformsNormal() { Flowable<Integer> source = Flowable.fromIterable(Arrays.asList(10, 20, 30)); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(o); + source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(subscriber); - verify(o, times(3)).onNext(1); - verify(o, times(3)).onNext(2); - verify(o, times(3)).onNext(3); - verify(o).onNext(4); - verify(o).onComplete(); + verify(subscriber, times(3)).onNext(1); + verify(subscriber, times(3)).onNext(2); + verify(subscriber, times(3)).onNext(3); + verify(subscriber).onNext(4); + verify(subscriber).onComplete(); - verify(o, never()).onNext(5); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(5); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -203,19 +202,18 @@ public void testFlatMapTransformsException() { Flowable.<Integer> error(new RuntimeException("Forced failure!")) ); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Subscriber<Object> o = TestHelper.mockSubscriber(); + source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(subscriber); - source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(o); + verify(subscriber, times(3)).onNext(1); + verify(subscriber, times(3)).onNext(2); + verify(subscriber, times(3)).onNext(3); + verify(subscriber).onNext(5); + verify(subscriber).onComplete(); + verify(subscriber, never()).onNext(4); - verify(o, times(3)).onNext(1); - verify(o, times(3)).onNext(2); - verify(o, times(3)).onNext(3); - verify(o).onNext(5); - verify(o).onComplete(); - verify(o, never()).onNext(4); - - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } <R> Callable<R> funcThrow0(R r) { @@ -238,18 +236,25 @@ public R apply(T t) { @Test public void testFlatMapTransformsOnNextFuncThrows() { - Flowable<Integer> onComplete = Flowable.fromIterable(Arrays.asList(4)); - Flowable<Integer> onError = Flowable.fromIterable(Arrays.asList(5)); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable<Integer> onComplete = Flowable.fromIterable(Arrays.asList(4)); + Flowable<Integer> onError = Flowable.fromIterable(Arrays.asList(5)); - Flowable<Integer> source = Flowable.fromIterable(Arrays.asList(10, 20, 30)); + Flowable<Integer> source = Flowable.fromIterable(Arrays.asList(10, 20, 30)); + + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Subscriber<Object> o = TestHelper.mockSubscriber(); + source.flatMap(funcThrow(1, onError), just(onError), just0(onComplete)).subscribe(subscriber); - source.flatMap(funcThrow(1, onError), just(onError), just0(onComplete)).subscribe(o); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -260,13 +265,13 @@ public void testFlatMapTransformsOnErrorFuncThrows() { Flowable<Integer> source = Flowable.error(new TestException()); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.flatMap(just(onNext), funcThrow((Throwable) null, onError), just0(onComplete)).subscribe(o); + source.flatMap(just(onNext), funcThrow((Throwable) null, onError), just0(onComplete)).subscribe(subscriber); - verify(o).onError(any(CompositeException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(CompositeException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -277,13 +282,13 @@ public void testFlatMapTransformsOnCompletedFuncThrows() { Flowable<Integer> source = Flowable.fromIterable(Arrays.<Integer> asList()); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.flatMap(just(onNext), just(onError), funcThrow0(onComplete)).subscribe(o); + source.flatMap(just(onNext), just(onError), funcThrow0(onComplete)).subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -294,13 +299,13 @@ public void testFlatMapTransformsMergeException() { Flowable<Integer> source = Flowable.fromIterable(Arrays.asList(10, 20, 30)); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.flatMap(just(onNext), just(onError), funcThrow0(onComplete)).subscribe(o); + source.flatMap(just(onNext), just(onError), funcThrow0(onComplete)).subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } private static <T> Flowable<T> composer(Flowable<T> source, final AtomicInteger subscriptionCount, final int m) { @@ -348,6 +353,7 @@ public Flowable<Integer> apply(Integer t1) { Assert.assertEquals(expected.size(), ts.valueCount()); Assert.assertTrue(expected.containsAll(ts.values())); } + @Test public void testFlatMapSelectorMaxConcurrent() { final int m = 4; @@ -411,8 +417,8 @@ public void testFlatMapTransformsMaxConcurrentNormal() { Flowable<Integer> source = Flowable.fromIterable(Arrays.asList(10, 20, 30)); - Subscriber<Object> o = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); Function<Integer, Flowable<Integer>> just = just(onNext); Function<Throwable, Flowable<Integer>> just2 = just(onError); @@ -423,14 +429,14 @@ public void testFlatMapTransformsMaxConcurrentNormal() { ts.assertNoErrors(); ts.assertTerminated(); - verify(o, times(3)).onNext(1); - verify(o, times(3)).onNext(2); - verify(o, times(3)).onNext(3); - verify(o).onNext(4); - verify(o).onComplete(); + verify(subscriber, times(3)).onNext(1); + verify(subscriber, times(3)).onNext(2); + verify(subscriber, times(3)).onNext(3); + verify(subscriber).onNext(4); + verify(subscriber).onComplete(); - verify(o, never()).onNext(5); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(5); + verify(subscriber, never()).onError(any(Throwable.class)); } @Ignore("Don't care for any reordering") @@ -471,6 +477,7 @@ public Flowable<Integer> apply(Integer t) { } } } + @Test(timeout = 30000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { @@ -514,7 +521,7 @@ public Flowable<Integer> apply(Integer t) { @Test public void flatMapIntPassthruAsync() { - for (int i = 0;i < 1000; i++) { + for (int i = 0; i < 1000; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.range(1, 1000).flatMap(new Function<Integer, Flowable<Integer>>() { @@ -530,6 +537,7 @@ public Flowable<Integer> apply(Integer t) { ts.assertValueCount(1000); } } + @Test public void flatMapTwoNestedSync() { for (final int n : new int[] { 1, 1000, 1000000 }) { @@ -656,11 +664,11 @@ public Flowable<Integer> apply(Integer v) { @Test public void castCrashUnsubscribes() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = TestSubscriber.create(); - ps.flatMap(new Function<Integer, Publisher<Integer>>() { + pp.flatMap(new Function<Integer, Publisher<Integer>>() { @Override public Publisher<Integer> apply(Integer t) { throw new TestException(); @@ -672,11 +680,11 @@ public Integer apply(Integer t1, Integer t2) { } }).subscribe(ts); - Assert.assertTrue("Not subscribed?", ps.hasSubscribers()); + Assert.assertTrue("Not subscribed?", pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - Assert.assertFalse("Subscribed?", ps.hasSubscribers()); + Assert.assertFalse("Subscribed?", pp.hasSubscribers()); ts.assertError(TestException.class); } @@ -780,68 +788,68 @@ public Object call() throws Exception { @Test public void scalarReentrant() { - final PublishProcessor<Flowable<Integer>> ps = PublishProcessor.create(); + final PublishProcessor<Flowable<Integer>> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps.onNext(Flowable.just(2)); + pp.onNext(Flowable.just(2)); } } }; - Flowable.merge(ps) - .subscribe(to); + Flowable.merge(pp) + .subscribe(ts); - ps.onNext(Flowable.just(1)); - ps.onComplete(); + pp.onNext(Flowable.just(1)); + pp.onComplete(); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void scalarReentrant2() { - final PublishProcessor<Flowable<Integer>> ps = PublishProcessor.create(); + final PublishProcessor<Flowable<Integer>> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps.onNext(Flowable.just(2)); + pp.onNext(Flowable.just(2)); } } }; - Flowable.merge(ps, 2) - .subscribe(to); + Flowable.merge(pp, 2) + .subscribe(ts); - ps.onNext(Flowable.just(1)); - ps.onComplete(); + pp.onNext(Flowable.just(1)); + pp.onComplete(); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void innerCompleteCancelRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = Flowable.merge(Flowable.just(ps)).test(); + final TestSubscriber<Integer> ts = Flowable.merge(Flowable.just(pp)).test(); Runnable r1 = new Runnable() { @Override public void run() { - ps.onComplete(); + pp.onComplete(); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -849,7 +857,6 @@ public void run() { } } - @Test public void fusedInnerThrows() { Flowable.just(1).hide() @@ -870,7 +877,7 @@ public Object apply(Integer w) throws Exception { @Test public void fusedInnerThrows2() { - TestSubscriber<Integer> to = Flowable.range(1, 2).hide() + TestSubscriber<Integer> ts = Flowable.range(1, 2).hide() .flatMap(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { @@ -885,7 +892,7 @@ public Integer apply(Integer w) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.errorList(to); + List<Throwable> errors = TestHelper.errorList(ts); TestHelper.assertError(errors, 0, TestException.class); @@ -930,7 +937,7 @@ public Object apply(Integer v) throws Exception { @Test public void cancelScalarDrainRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -962,7 +969,7 @@ public void run() { @Test public void cancelDrainRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { for (int j = 1; j < 50; j += 5) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -1033,4 +1040,144 @@ public Object apply(Integer v, Object w) throws Exception { .test() .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null Publisher"); } + + @Test + public void failingFusedInnerCancelsSource() { + final AtomicInteger counter = new AtomicInteger(); + Flowable.range(1, 5) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .flatMap(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.<Integer>fromIterable(new Iterable<Integer>() { + @Override + public Iterator<Integer> iterator() { + return new Iterator<Integer>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }); + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void maxConcurrencySustained() { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); + PublishProcessor<Integer> pp3 = PublishProcessor.create(); + PublishProcessor<Integer> pp4 = PublishProcessor.create(); + + TestSubscriber<Integer> ts = Flowable.just(pp1, pp2, pp3, pp4) + .flatMap(new Function<PublishProcessor<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(PublishProcessor<Integer> v) throws Exception { + return v; + } + }, 2) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + if (v == 1) { + // this will make sure the drain loop detects two completed + // inner sources and replaces them with fresh ones + pp1.onComplete(); + pp2.onComplete(); + } + } + }) + .test(); + + pp1.onNext(1); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + assertTrue(pp3.hasSubscribers()); + assertTrue(pp4.hasSubscribers()); + + ts.dispose(); + + assertFalse(pp3.hasSubscribers()); + assertFalse(pp4.hasSubscribers()); + } + + @Test + public void mainErrorsInnerCancelled() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + + pp1 + .flatMap(Functions.justFunction(pp2)) + .test(); + + pp1.onNext(1); + assertTrue("No subscribers?", pp2.hasSubscribers()); + + pp1.onError(new TestException()); + + assertFalse("Has subscribers?", pp2.hasSubscribers()); + } + + @Test + public void innerErrorsMainCancelled() { + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + + pp1 + .flatMap(Functions.justFunction(pp2)) + .test(); + + pp1.onNext(1); + assertTrue("No subscribers?", pp2.hasSubscribers()); + + pp2.onError(new TestException()); + + assertFalse("Has subscribers?", pp1.hasSubscribers()); + } + + @Test(timeout = 5000) + public void mixedScalarAsync() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + Flowable + .range(0, 20) + .flatMap(new Function<Integer, Publisher<?>>() { + @Override + public Publisher<?> apply(Integer integer) throws Exception { + if (integer % 5 != 0) { + return Flowable + .just(integer); + } + + return Flowable + .just(-integer) + .observeOn(Schedulers.computation()); + } + }, false, 1) + .ignoreElements() + .blockingAwait(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java index a865475acf..9baf8f2ea7 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterableTest.java @@ -17,7 +17,7 @@ import java.util.*; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.*; import org.reactivestreams.*; @@ -26,9 +26,11 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.operators.flowable.FlowableFlattenIterable.FlattenIterableSubscriber; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.*; @@ -393,9 +395,9 @@ public void remove() { } }; - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps + pp .concatMapIterable(new Function<Integer, Iterable<Integer>>() { @Override public Iterable<Integer> apply(Integer v) { @@ -404,13 +406,13 @@ public Iterable<Integer> apply(Integer v) { }) .subscribe(ts); - ps.onNext(1); + pp.onNext(1); ts.assertNoValues(); ts.assertError(TestException.class); ts.assertNotComplete(); - Assert.assertFalse("PublishProcessor has Subscribers?!", ps.hasSubscribers()); + Assert.assertFalse("PublishProcessor has Subscribers?!", pp.hasSubscribers()); } @Test @@ -496,9 +498,9 @@ public void remove() { } }; - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps + pp .concatMapIterable(new Function<Integer, Iterable<Integer>>() { @Override public Iterable<Integer> apply(Integer v) { @@ -508,13 +510,13 @@ public Iterable<Integer> apply(Integer v) { .take(1) .subscribe(ts); - ps.onNext(1); + pp.onNext(1); ts.assertValue(1); ts.assertNoErrors(); ts.assertComplete(); - Assert.assertFalse("PublishProcessor has Subscribers?!", ps.hasSubscribers()); + Assert.assertFalse("PublishProcessor has Subscribers?!", pp.hasSubscribers()); Assert.assertEquals(1, counter.get()); } @@ -581,8 +583,8 @@ public Iterable<Integer> apply(Object v) throws Exception { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return o.flatMapIterable(new Function<Object, Iterable<Integer>>() { + public Object apply(Flowable<Integer> f) throws Exception { + return f.flatMapIterable(new Function<Object, Iterable<Integer>>() { @Override public Iterable<Integer> apply(Object v) throws Exception { return Arrays.asList(10, 20); @@ -615,7 +617,7 @@ public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") QueueSubscription<Integer> qs = (QueueSubscription<Integer>)s; - assertEquals(QueueSubscription.SYNC, qs.requestFusion(QueueSubscription.ANY)); + assertEquals(QueueFuseable.SYNC, qs.requestFusion(QueueFuseable.ANY)); try { assertFalse("Source reports being empty!", qs.isEmpty()); @@ -670,7 +672,7 @@ public void smallPrefetch2() { @Test public void mixedInnerSource() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 2, 3) .flatMapIterable(new Function<Integer, Iterable<Integer>>() { @@ -684,13 +686,13 @@ public Iterable<Integer> apply(Integer v) throws Exception { }) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2, 1, 2); } @Test public void mixedInnerSource2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 2, 3) .flatMapIterable(new Function<Integer, Iterable<Integer>>() { @@ -704,13 +706,13 @@ public Iterable<Integer> apply(Integer v) throws Exception { }) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2); } @Test public void fusionRejected() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1, 2, 3).hide() .flatMapIterable(new Function<Integer, Iterable<Integer>>() { @@ -721,7 +723,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { }) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 1, 2, 1, 2); } @@ -743,7 +745,7 @@ public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") QueueSubscription<Integer> qs = (QueueSubscription<Integer>)s; - assertEquals(QueueSubscription.SYNC, qs.requestFusion(QueueSubscription.ANY)); + assertEquals(QueueFuseable.SYNC, qs.requestFusion(QueueFuseable.ANY)); try { assertFalse("Source reports being empty!", qs.isEmpty()); @@ -914,4 +916,185 @@ public void multiShareHidden() { .assertResult(600L); } } + + @Test + public void failingInnerCancelsSource() { + final AtomicInteger counter = new AtomicInteger(); + Flowable.range(1, 5) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .flatMapIterable(new Function<Integer, Iterable<Integer>>() { + @Override + public Iterable<Integer> apply(Integer v) + throws Exception { + return new Iterable<Integer>() { + @Override + public Iterator<Integer> iterator() { + return new Iterator<Integer>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f.flatMapIterable(Functions.justFunction(Collections.emptyList())); + } + }); + } + + @Test + public void upstreamFusionRejected() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + FlattenIterableSubscriber<Integer, Integer> f = new FlattenIterableSubscriber<Integer, Integer>(ts, + Functions.justFunction(Collections.<Integer>emptyList()), 128); + + final AtomicLong requested = new AtomicLong(); + + f.onSubscribe(new QueueSubscription<Integer>() { + + @Override + public int requestFusion(int mode) { + return 0; + } + + @Override + public boolean offer(Integer value) { + return false; + } + + @Override + public boolean offer(Integer v1, Integer v2) { + return false; + } + + @Override + public Integer poll() throws Exception { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void clear() { + } + + @Override + public void request(long n) { + requested.set(n); + } + + @Override + public void cancel() { + } + }); + + assertEquals(128, requested.get()); + assertNotNull(f.queue); + + ts.assertEmpty(); + } + + @Test + public void onErrorLate() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + FlattenIterableSubscriber<Integer, Integer> f = new FlattenIterableSubscriber<Integer, Integer>(ts, + Functions.justFunction(Collections.<Integer>emptyList()), 128); + + f.onSubscribe(new BooleanSubscription()); + + f.onError(new TestException("first")); + + ts.assertFailureAndMessage(TestException.class, "first"); + + assertTrue(errors.isEmpty()); + + f.done = false; + f.onError(new TestException("second")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.never().flatMapIterable(Functions.justFunction(Collections.emptyList()))); + } + + @Test + public void fusedCurrentIteratorEmpty() throws Exception { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + FlattenIterableSubscriber<Integer, Integer> f = new FlattenIterableSubscriber<Integer, Integer>(ts, + Functions.justFunction(Arrays.<Integer>asList(1, 2)), 128); + + f.onSubscribe(new BooleanSubscription()); + + f.onNext(1); + + assertFalse(f.isEmpty()); + + assertEquals(1, f.poll().intValue()); + + assertFalse(f.isEmpty()); + + assertEquals(2, f.poll().intValue()); + + assertTrue(f.isEmpty()); + } + + @Test + public void fusionRequestedState() throws Exception { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + FlattenIterableSubscriber<Integer, Integer> f = new FlattenIterableSubscriber<Integer, Integer>(ts, + Functions.justFunction(Arrays.<Integer>asList(1, 2)), 128); + + f.onSubscribe(new BooleanSubscription()); + + f.fusionMode = QueueFuseable.NONE; + + assertEquals(QueueFuseable.NONE, f.requestFusion(QueueFuseable.SYNC)); + + assertEquals(QueueFuseable.NONE, f.requestFusion(QueueFuseable.ASYNC)); + + f.fusionMode = QueueFuseable.SYNC; + + assertEquals(QueueFuseable.SYNC, f.requestFusion(QueueFuseable.SYNC)); + + assertEquals(QueueFuseable.NONE, f.requestFusion(QueueFuseable.ASYNC)); +} } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java index b07fe3bf2d..2112541c29 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromArrayTest.java @@ -33,6 +33,7 @@ Flowable<Integer> create(int n) { } return Flowable.fromArray(array); } + @Test public void simple() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java index 9e93e15e4d..254274b1cf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java @@ -16,8 +16,10 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; +import java.util.List; import java.util.concurrent.*; import org.junit.Test; @@ -26,6 +28,9 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -56,13 +61,13 @@ public void shouldCallOnNextAndOnCompleted() throws Exception { Flowable<String> fromCallableFlowable = Flowable.fromCallable(func); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - fromCallableFlowable.subscribe(observer); + fromCallableFlowable.subscribe(subscriber); - verify(observer).onNext("test_value"); - verify(observer).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber).onNext("test_value"); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @SuppressWarnings("unchecked") @@ -75,13 +80,13 @@ public void shouldCallOnError() throws Exception { Flowable<Object> fromCallableFlowable = Flowable.fromCallable(func); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - fromCallableFlowable.subscribe(observer); + fromCallableFlowable.subscribe(subscriber); - verify(observer, never()).onNext(any()); - verify(observer, never()).onComplete(); - verify(observer).onError(throwable); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(throwable); } @SuppressWarnings("unchecked") @@ -112,9 +117,9 @@ public String answer(InvocationOnMock invocation) throws Throwable { Flowable<String> fromCallableFlowable = Flowable.fromCallable(func); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<String> outer = new TestSubscriber<String>(observer); + TestSubscriber<String> outer = new TestSubscriber<String>(subscriber); fromCallableFlowable .subscribeOn(Schedulers.computation()) @@ -133,8 +138,8 @@ public String answer(InvocationOnMock invocation) throws Throwable { verify(func).call(); // Observer must not be notified at all - verify(observer).onSubscribe(any(Subscription.class)); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe(any(Subscription.class)); + verifyNoMoreInteractions(subscriber); } @Test @@ -148,12 +153,115 @@ public Object call() throws Exception { } }); - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - fromCallableFlowable.subscribe(observer); + fromCallableFlowable.subscribe(subscriber); - verify(observer).onSubscribe(any(Subscription.class)); - verify(observer).onError(checkedException); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe(any(Subscription.class)); + verify(subscriber).onError(checkedException); + verifyNoMoreInteractions(subscriber); + } + + @Test + public void fusedFlatMapExecution() { + final int[] calls = { 0 }; + + Flowable.just(1).flatMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) + throws Exception { + return Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return ++calls[0]; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(1, calls[0]); + } + + @Test + public void fusedFlatMapExecutionHidden() { + final int[] calls = { 0 }; + + Flowable.just(1).hide().flatMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) + throws Exception { + return Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return ++calls[0]; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(1, calls[0]); + } + + @Test + public void fusedFlatMapNull() { + Flowable.just(1).flatMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) + throws Exception { + return Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return null; + } + }); + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void fusedFlatMapNullHidden() { + Flowable.just(1).hide().flatMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) + throws Exception { + return Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return null; + } + }); + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test(timeout = 5000) + public void undeliverableUponCancellation() throws Exception { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + ts.cancel(); + throw new TestException(); + } + }) + .subscribe(ts); + + ts.assertEmpty(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java index 877222aca3..d2e15fa60d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -46,15 +45,15 @@ public void testNull() { public void testListIterable() { Flowable<String> flowable = Flowable.fromIterable(Arrays.<String> asList("one", "two", "three")); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - flowable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } /** @@ -90,30 +89,30 @@ public void remove() { }; Flowable<String> flowable = Flowable.fromIterable(it); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - flowable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext("1"); - verify(observer, times(1)).onNext("2"); - verify(observer, times(1)).onNext("3"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("1"); + verify(subscriber, times(1)).onNext("2"); + verify(subscriber, times(1)).onNext("3"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testObservableFromIterable() { Flowable<String> flowable = Flowable.fromIterable(Arrays.<String> asList("one", "two", "three")); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - flowable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -122,14 +121,14 @@ public void testBackpressureViaRequest() { for (int i = 1; i <= Flowable.bufferSize() + 1; i++) { list.add(i); } - Flowable<Integer> o = Flowable.fromIterable(list); + Flowable<Integer> f = Flowable.fromIterable(list); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); ts.assertNoValues(); ts.request(1); - o.subscribe(ts); + f.subscribe(ts); ts.assertValue(1); ts.request(2); @@ -142,14 +141,14 @@ public void testBackpressureViaRequest() { @Test public void testNoBackpressure() { - Flowable<Integer> o = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5)); + Flowable<Integer> f = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5)); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); ts.assertNoValues(); ts.request(Long.MAX_VALUE); // infinite - o.subscribe(ts); + f.subscribe(ts); ts.assertValues(1, 2, 3, 4, 5); ts.assertTerminated(); @@ -157,12 +156,12 @@ public void testNoBackpressure() { @Test public void testSubscribeMultipleTimes() { - Flowable<Integer> o = Flowable.fromIterable(Arrays.asList(1, 2, 3)); + Flowable<Integer> f = Flowable.fromIterable(Arrays.asList(1, 2, 3)); for (int i = 0; i < 10; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - o.subscribe(ts); + f.subscribe(ts); ts.assertValues(1, 2, 3); ts.assertNoErrors(); @@ -172,12 +171,12 @@ public void testSubscribeMultipleTimes() { @Test public void testFromIterableRequestOverflow() throws InterruptedException { - Flowable<Integer> o = Flowable.fromIterable(Arrays.asList(1,2,3,4)); + Flowable<Integer> f = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4)); final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); - o.subscribeOn(Schedulers.computation()) + f.subscribeOn(Schedulers.computation()) .subscribe(new DefaultSubscriber<Integer>() { @Override @@ -551,7 +550,7 @@ public void remove() { @Test public void fusionWithConcatMap() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.fromIterable(Arrays.asList(1, 2, 3, 4)).concatMap( new Function<Integer, Flowable <Integer>>() { @@ -559,11 +558,11 @@ public void fusionWithConcatMap() { public Flowable<Integer> apply(Integer v) { return Flowable.range(v, 2); } - }).subscribe(to); + }).subscribe(ts); - to.assertValues(1, 2, 2, 3, 3, 4, 4, 5); - to.assertNoErrors(); - to.assertComplete(); + ts.assertValues(1, 2, 2, 3, 3, 4, 4, 5); + ts.assertNoErrors(); + ts.assertComplete(); } @Test @@ -722,7 +721,7 @@ public void normalConditionalLong2() { @Test public void requestRaceConditional() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r = new Runnable() { @@ -736,13 +735,13 @@ public void run() { .filter(Functions.alwaysTrue()) .subscribe(ts); - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @Test public void requestRaceConditional2() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r = new Runnable() { @@ -756,13 +755,13 @@ public void run() { .filter(Functions.alwaysFalse()) .subscribe(ts); - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @Test public void requestCancelConditionalRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r1 = new Runnable() { @@ -783,13 +782,13 @@ public void run() { .filter(Functions.alwaysTrue()) .subscribe(ts); - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void requestCancelConditionalRace2() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r1 = new Runnable() { @@ -810,13 +809,13 @@ public void run() { .filter(Functions.alwaysTrue()) .subscribe(ts); - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void requestCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r1 = new Runnable() { @@ -836,13 +835,13 @@ public void run() { Flowable.fromIterable(Arrays.asList(1, 2, 3, 4)) .subscribe(ts); - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void requestCancelRace2() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); Runnable r1 = new Runnable() { @@ -862,18 +861,18 @@ public void run() { Flowable.fromIterable(Arrays.asList(1, 2, 3, 4)) .subscribe(ts); - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void fusionRejected() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); Flowable.fromIterable(Arrays.asList(1, 2, 3)) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3); } @@ -882,21 +881,21 @@ public void fusionClear() { Flowable.fromIterable(Arrays.asList(1, 2, 3)) .subscribe(new FlowableSubscriber<Integer>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { @SuppressWarnings("unchecked") - QueueSubscription<Integer> qd = (QueueSubscription<Integer>)d; + QueueSubscription<Integer> qs = (QueueSubscription<Integer>)s; - qd.requestFusion(QueueSubscription.ANY); + qs.requestFusion(QueueFuseable.ANY); try { - assertEquals(1, qd.poll().intValue()); + assertEquals(1, qs.poll().intValue()); } catch (Throwable ex) { fail(ex.toString()); } - qd.clear(); + qs.clear(); try { - assertNull(qd.poll()); + assertNull(qs.poll()); } catch (Throwable ex) { fail(ex.toString()); } @@ -932,7 +931,7 @@ public void hasNext2Throws() { @Test public void hasNextCancels() { - final TestSubscriber<Integer> to = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.fromIterable(new Iterable<Integer>() { @Override @@ -943,7 +942,7 @@ public Iterator<Integer> iterator() { @Override public boolean hasNext() { if (++count == 2) { - to.cancel(); + ts.cancel(); } return true; } @@ -960,9 +959,9 @@ public void remove() { }; } }) - .subscribe(to); + .subscribe(ts); - to.assertValue(1) + ts.assertValue(1) .assertNoErrors() .assertNotComplete(); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java index 2aa29ffca8..b2452a65fb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromSourceTest.java @@ -13,12 +13,15 @@ package io.reactivex.internal.operators.flowable; +import java.util.List; + import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Cancellable; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.*; @@ -37,7 +40,6 @@ public void before() { ts = new TestSubscriber<Integer>(0L); } - @Test public void normalBuffered() { Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts); @@ -122,7 +124,6 @@ public void normalMissingRequested() { ts.assertComplete(); } - @Test public void normalError() { Flowable.create(source, BackpressureStrategy.ERROR).subscribe(ts); @@ -271,82 +272,116 @@ public void unsubscribedMissing() { @Test public void unsubscribedNoCancelBuffer() { - Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts); - ts.cancel(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts); + ts.cancel(); - sourceNoCancel.onNext(1); - sourceNoCancel.onNext(2); - sourceNoCancel.onError(new TestException()); + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); - ts.request(1); + ts.request(1); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void unsubscribedNoCancelLatest() { - Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts); - ts.cancel(); - - sourceNoCancel.onNext(1); - sourceNoCancel.onNext(2); - sourceNoCancel.onError(new TestException()); - - ts.request(1); - - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts); + ts.cancel(); + + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); + + ts.request(1); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void unsubscribedNoCancelError() { - Flowable.create(sourceNoCancel, BackpressureStrategy.ERROR).subscribe(ts); - ts.cancel(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(sourceNoCancel, BackpressureStrategy.ERROR).subscribe(ts); + ts.cancel(); - sourceNoCancel.onNext(1); - sourceNoCancel.onNext(2); - sourceNoCancel.onError(new TestException()); + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); - ts.request(1); + ts.request(1); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void unsubscribedNoCancelDrop() { - Flowable.create(sourceNoCancel, BackpressureStrategy.DROP).subscribe(ts); - ts.cancel(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(sourceNoCancel, BackpressureStrategy.DROP).subscribe(ts); + ts.cancel(); - sourceNoCancel.onNext(1); - sourceNoCancel.onNext(2); - sourceNoCancel.onError(new TestException()); + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); - ts.request(1); + ts.request(1); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void unsubscribedNoCancelMissing() { - Flowable.create(sourceNoCancel, BackpressureStrategy.MISSING).subscribe(ts); - ts.cancel(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(sourceNoCancel, BackpressureStrategy.MISSING).subscribe(ts); + ts.cancel(); - sourceNoCancel.onNext(1); - sourceNoCancel.onNext(2); - sourceNoCancel.onError(new TestException()); + sourceNoCancel.onNext(1); + sourceNoCancel.onNext(2); + sourceNoCancel.onError(new TestException()); - ts.request(1); + ts.request(1); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -452,7 +487,6 @@ public void unsubscribeNoCancel() { ts.assertNotComplete(); } - @Test public void unsubscribeInline() { TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { @@ -622,12 +656,12 @@ public void onNext(Integer t) { static final class PublishAsyncEmitter implements FlowableOnSubscribe<Integer>, FlowableSubscriber<Integer> { - final PublishProcessor<Integer> subject; + final PublishProcessor<Integer> processor; FlowableEmitter<Integer> current; PublishAsyncEmitter() { - this.subject = PublishProcessor.create(); + this.processor = PublishProcessor.create(); } long requested() { @@ -658,7 +692,7 @@ public void onNext(Integer v) { }; - subject.subscribe(as); + processor.subscribe(as); t.setCancellable(new Cancellable() { @Override @@ -675,32 +709,32 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Integer t) { - subject.onNext(t); + processor.onNext(t); } @Override public void onError(Throwable e) { - subject.onError(e); + processor.onError(e); } @Override public void onComplete() { - subject.onComplete(); + processor.onComplete(); } } static final class PublishAsyncEmitterNoCancel implements FlowableOnSubscribe<Integer>, FlowableSubscriber<Integer> { - final PublishProcessor<Integer> subject; + final PublishProcessor<Integer> processor; PublishAsyncEmitterNoCancel() { - this.subject = PublishProcessor.create(); + this.processor = PublishProcessor.create(); } @Override public void subscribe(final FlowableEmitter<Integer> t) { - subject.subscribe(new FlowableSubscriber<Integer>() { + processor.subscribe(new FlowableSubscriber<Integer>() { @Override public void onSubscribe(Subscription s) { @@ -732,17 +766,17 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Integer t) { - subject.onNext(t); + processor.onNext(t); } @Override public void onError(Throwable e) { - subject.onError(e); + processor.onError(e); } @Override public void onComplete() { - subject.onComplete(); + processor.onComplete(); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java index a90d91455a..73a7401588 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGenerateTest.java @@ -219,7 +219,7 @@ public void accept(Object s, Emitter<Object> e) throws Exception { } }, Functions.emptyConsumer()); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Object> ts = source.test(0L); Runnable r = new Runnable() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java index afbe8e1108..cb52621bac 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java @@ -17,6 +17,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.io.IOException; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -25,15 +26,20 @@ import org.mockito.Mockito; import org.reactivestreams.*; +import com.google.common.base.Ticker; +import com.google.common.cache.*; + import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.flowables.GroupedFlowable; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; import io.reactivex.subscribers.*; public class FlowableGroupByTest { @@ -97,7 +103,7 @@ public void testEmpty() { @Test public void testError() { Flowable<String> sourceStrings = Flowable.just("one", "two", "three", "four", "five", "six"); - Flowable<String> errorSource = Flowable.error(new RuntimeException("forced failure")); + Flowable<String> errorSource = Flowable.error(new TestException("forced failure")); Flowable<String> source = Flowable.concat(sourceStrings, errorSource); Flowable<GroupedFlowable<Integer, String>> grouped = source.groupBy(length); @@ -109,13 +115,13 @@ public void testError() { grouped.flatMap(new Function<GroupedFlowable<Integer, String>, Flowable<String>>() { @Override - public Flowable<String> apply(final GroupedFlowable<Integer, String> o) { + public Flowable<String> apply(final GroupedFlowable<Integer, String> f) { groupCounter.incrementAndGet(); - return o.map(new Function<String, String>() { + return f.map(new Function<String, String>() { @Override public String apply(String v) { - return "Event => key: " + o.getKey() + " value: " + v; + return "Event => key: " + f.getKey() + " value: " + v; } }); } @@ -128,7 +134,7 @@ public void onComplete() { @Override public void onError(Throwable e) { - e.printStackTrace(); +// e.printStackTrace(); error.set(e); } @@ -143,22 +149,24 @@ public void onNext(String v) { assertEquals(3, groupCounter.get()); assertEquals(6, eventCounter.get()); assertNotNull(error.get()); + assertTrue("" + error.get(), error.get() instanceof TestException); + assertEquals(error.get().getMessage(), "forced failure"); } - private static <K, V> Map<K, Collection<V>> toMap(Flowable<GroupedFlowable<K, V>> observable) { + private static <K, V> Map<K, Collection<V>> toMap(Flowable<GroupedFlowable<K, V>> flowable) { final ConcurrentHashMap<K, Collection<V>> result = new ConcurrentHashMap<K, Collection<V>>(); - observable.blockingForEach(new Consumer<GroupedFlowable<K, V>>() { + flowable.blockingForEach(new Consumer<GroupedFlowable<K, V>>() { @Override - public void accept(final GroupedFlowable<K, V> o) { - result.put(o.getKey(), new ConcurrentLinkedQueue<V>()); - o.subscribe(new Consumer<V>() { + public void accept(final GroupedFlowable<K, V> f) { + result.put(f.getKey(), new ConcurrentLinkedQueue<V>()); + f.subscribe(new Consumer<V>() { @Override public void accept(V v) { - result.get(o.getKey()).add(v); + result.get(f.getKey()).add(v); } }); @@ -186,8 +194,8 @@ public void testGroupedEventStream() throws Throwable { Flowable<Event> es = Flowable.unsafeCreate(new Publisher<Event>() { @Override - public void subscribe(final Subscriber<? super Event> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super Event> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("*** Subscribing to EventStream ***"); subscribeCounter.incrementAndGet(); new Thread(new Runnable() { @@ -198,9 +206,9 @@ public void run() { Event e = new Event(); e.source = i % groupCount; e.message = "Event-" + i; - observer.onNext(e); + subscriber.onNext(e); } - observer.onComplete(); + subscriber.onComplete(); } }).start(); @@ -993,18 +1001,16 @@ public void testGroupByOnAsynchronousSourceAcceptsMultipleSubscriptions() throws Flowable<GroupedFlowable<Boolean, Long>> stream = source.groupBy(IS_EVEN); // create two observers - @SuppressWarnings("unchecked") - DefaultSubscriber<GroupedFlowable<Boolean, Long>> o1 = mock(DefaultSubscriber.class); - @SuppressWarnings("unchecked") - DefaultSubscriber<GroupedFlowable<Boolean, Long>> o2 = mock(DefaultSubscriber.class); + Subscriber<GroupedFlowable<Boolean, Long>> f1 = TestHelper.mockSubscriber(); + Subscriber<GroupedFlowable<Boolean, Long>> f2 = TestHelper.mockSubscriber(); // subscribe with the observers - stream.subscribe(o1); - stream.subscribe(o2); + stream.subscribe(f1); + stream.subscribe(f2); // check that subscriptions were successful - verify(o1, never()).onError(Mockito.<Throwable> any()); - verify(o2, never()).onError(Mockito.<Throwable> any()); + verify(f1, never()).onError(Mockito.<Throwable> any()); + verify(f2, never()).onError(Mockito.<Throwable> any()); } private static Function<Long, Boolean> IS_EVEN = new Function<Long, Boolean>() { @@ -1222,14 +1228,13 @@ public void accept(GroupedFlowable<Integer, Integer> t1) { inner.get().subscribe(); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o2 = mock(DefaultSubscriber.class); + Subscriber<Integer> subscriber2 = TestHelper.mockSubscriber(); - inner.get().subscribe(o2); + inner.get().subscribe(subscriber2); - verify(o2, never()).onComplete(); - verify(o2, never()).onNext(anyInt()); - verify(o2).onError(any(IllegalStateException.class)); + verify(subscriber2, never()).onComplete(); + verify(subscriber2, never()).onNext(anyInt()); + verify(subscriber2).onError(any(IllegalStateException.class)); } @Test @@ -1381,7 +1386,7 @@ public void accept(String s) { @Test public void testGroupByUnsubscribe() { final Subscription s = mock(Subscription.class); - Flowable<Integer> o = Flowable.unsafeCreate( + Flowable<Integer> f = Flowable.unsafeCreate( new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> subscriber) { @@ -1391,7 +1396,7 @@ public void subscribe(Subscriber<? super Integer> subscriber) { ); TestSubscriber<Object> ts = new TestSubscriber<Object>(); - o.groupBy(new Function<Integer, Integer>() { + f.groupBy(new Function<Integer, Integer>() { @Override public Integer apply(Integer integer) { @@ -1422,11 +1427,11 @@ public void onError(Throwable e) { } @Override - public void onNext(GroupedFlowable<Integer, Integer> o) { - if (o.getKey() == 0) { - o.subscribe(inner1); + public void onNext(GroupedFlowable<Integer, Integer> f) { + if (f.getKey() == 0) { + f.subscribe(inner1); } else { - o.subscribe(inner2); + f.subscribe(inner2); } } }); @@ -1598,9 +1603,9 @@ public void accept(GroupedFlowable<Object, Integer> g) { @Test public void outerInnerFusion() { - final TestSubscriber<Integer> ts1 = SubscriberFusion.newTest(QueueSubscription.ANY); + final TestSubscriber<Integer> ts1 = SubscriberFusion.newTest(QueueFuseable.ANY); - final TestSubscriber<GroupedFlowable<Integer, Integer>> ts2 = SubscriberFusion.newTest(QueueSubscription.ANY); + final TestSubscriber<GroupedFlowable<Integer, Integer>> ts2 = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 10).groupBy(new Function<Integer, Integer>() { @Override @@ -1622,19 +1627,18 @@ public void accept(GroupedFlowable<Integer, Integer> g) { .subscribe(ts2); ts1 - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertValues(2, 3, 4, 5, 6, 7, 8, 9, 10, 11) .assertNoErrors() .assertComplete(); ts2 - .assertOf(SubscriberFusion.<GroupedFlowable<Integer, Integer>>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<GroupedFlowable<Integer, Integer>>assertFusionMode(QueueFuseable.ASYNC)) .assertValueCount(1) .assertNoErrors() .assertComplete(); } - @Test public void keySelectorAndDelayError() { Flowable.just(1).concatWith(Flowable.<Integer>error(new TestException())) @@ -1680,47 +1684,47 @@ public void accept(GroupedFlowable<Integer, Integer> g) throws Exception { @Test public void reentrantComplete() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps.onComplete(); + pp.onComplete(); } } }; - Flowable.merge(ps.groupBy(Functions.justFunction(1))) - .subscribe(to); + Flowable.merge(pp.groupBy(Functions.justFunction(1))) + .subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertResult(1); + ts.assertResult(1); } @Test public void reentrantCompleteCancel() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { - ps.onComplete(); + pp.onComplete(); dispose(); } } }; - Flowable.merge(ps.groupBy(Functions.justFunction(1))) - .subscribe(to); + Flowable.merge(pp.groupBy(Functions.justFunction(1))) + .subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertSubscribed().assertValue(1).assertNoErrors().assertNotComplete(); + ts.assertSubscribed().assertValue(1).assertNoErrors().assertNotComplete(); } @Test @@ -1734,13 +1738,13 @@ public void delayErrorSimpleComplete() { @Test public void mainFusionRejected() { - TestSubscriber<Flowable<Integer>> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Flowable<Integer>> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.just(1) .groupBy(Functions.justFunction(1)) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertValueCount(1) .assertComplete() .assertNoErrors(); @@ -1793,25 +1797,25 @@ public Publisher<Integer> apply(GroupedFlowable<Object, Integer> g) throws Excep @Test public void errorFused() { - TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.error(new TestException()) .groupBy(Functions.justFunction(1)) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertFailure(TestException.class); } @Test public void errorFusedDelayed() { - TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.error(new TestException()) .groupBy(Functions.justFunction(1), true) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertFailure(TestException.class); } @@ -1842,4 +1846,508 @@ public Publisher<Integer> apply(GroupedFlowable<Integer, Integer> g) throws Exce .test() .assertResult(1); } + + @Test + public void mapFactoryThrows() { + final IOException ex = new IOException("boo"); + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = // + new Function<Consumer<Object>, Map<Integer, Object>>() { + + @Override + public Map<Integer, Object> apply(final Consumer<Object> notify) throws Exception { + throw ex; + } + }; + Flowable.just(1) + .groupBy(Functions.<Integer>identity(), Functions.identity(), true, 16, evictingMapFactory) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void mapFactoryExpiryCompletesGroupedFlowable() { + final List<Integer> completed = new CopyOnWriteArrayList<Integer>(); + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject<Integer> subject = PublishSubject.create(); + TestSubscriber<Integer> ts = subject.toFlowable(BackpressureStrategy.BUFFER) + .groupBy(Functions.<Integer>identity(), Functions.<Integer>identity(), true, 16, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test(); + subject.onNext(1); + subject.onNext(2); + subject.onNext(3); + ts.assertValues(1, 2, 3) + .assertNotTerminated(); + assertEquals(Arrays.asList(1, 2), completed); + //ensure coverage of the code that clears the evicted queue + subject.onComplete(); + ts.assertComplete(); + ts.assertValueCount(3); + } + + private static final Function<Integer, Integer> mod5 = new Function<Integer, Integer>() { + + @Override + public Integer apply(Integer n) throws Exception { + return n % 5; + } + }; + + @Test + public void mapFactoryWithExpiringGuavaCacheDemonstrationCodeForUseInJavadoc() { + //javadoc will be a version of this using lambdas and without assertions + final List<Integer> completed = new CopyOnWriteArrayList<Integer>(); + //size should be less than 5 to notice the effect + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = createEvictingMapFactoryGuava(3); + int numValues = 1000; + TestSubscriber<Integer> ts = + Flowable.range(1, numValues) + .groupBy(mod5, Functions.<Integer>identity(), true, 16, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test() + .assertComplete(); + ts.assertValueCount(numValues); + //the exact eviction behaviour of the guava cache is not specified so we make some approximate tests + assertTrue(completed.size() > numValues * 0.9); + } + + @Test + public void mapFactoryEvictionQueueClearedOnErrorCoverageOnly() { + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject<Integer> subject = PublishSubject.create(); + TestSubscriber<Integer> ts = subject + .toFlowable(BackpressureStrategy.BUFFER) + .groupBy(Functions.<Integer>identity(), Functions.<Integer>identity(), true, 16, evictingMapFactory) + .flatMap(new Function<GroupedFlowable<Integer, Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(GroupedFlowable<Integer, Integer> g) throws Exception { + return g; + } + }) + .test(); + RuntimeException ex = new RuntimeException(); + //ensure coverage of the code that clears the evicted queue + subject.onError(ex); + ts.assertNoValues() + .assertError(ex); + } + + private static Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Integer>> addCompletedKey( + final List<Integer> completed) { + return new Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(final GroupedFlowable<Integer, Integer> g) throws Exception { + return g.doOnComplete(new Action() { + @Override + public void run() throws Exception { + completed.add(g.getKey()); + } + }); + } + }; + } + + private static final class TestTicker extends Ticker { + long tick; + + @Override + public long read() { + return tick; + } + } + + @Test + public void testGroupByEvictionCancellationOfSource5933() { + PublishProcessor<Integer> source = PublishProcessor.create(); + final TestTicker testTicker = new TestTicker(); + + Function<Consumer<Object>, Map<Integer, Object>> mapFactory = new Function<Consumer<Object>, Map<Integer, Object>>() { + @Override + public Map<Integer, Object> apply(final Consumer<Object> action) throws Exception { + return CacheBuilder.newBuilder() // + .expireAfterAccess(5, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() { + @Override + public void onRemoval(RemovalNotification<Object, Object> notification) { + try { + action.accept(notification.getValue()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + }).ticker(testTicker) // + .<Integer, Object>build().asMap(); + } + }; + + final List<String> list = new CopyOnWriteArrayList<String>(); + Flowable<Integer> stream = source // + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + list.add("Source canceled"); + } + }) + .<Integer, Integer>groupBy(Functions.<Integer>identity(), Functions.<Integer>identity(), false, + Flowable.bufferSize(), mapFactory) // + .flatMap(new Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(GroupedFlowable<Integer, Integer> group) + throws Exception { + return group // + .doOnComplete(new Action() { + @Override + public void run() throws Exception { + list.add("Group completed"); + } + }).doOnCancel(new Action() { + @Override + public void run() throws Exception { + list.add("Group canceled"); + } + }); + } + }); + TestSubscriber<Integer> ts = stream // + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + list.add("Outer group by canceled"); + } + }).test(); + + // Send 3 in the same group and wait for them to be seen + source.onNext(1); + source.onNext(1); + source.onNext(1); + ts.awaitCount(3); + + // Advance time far enough to evict the group. + // NOTE -- Comment this line out to make the test "pass". + testTicker.tick = TimeUnit.SECONDS.toNanos(6); + + // Send more data in the group (triggering eviction and recreation) + source.onNext(1); + + // Wait for the last 2 and then cancel the subscription + ts.awaitCount(4); + ts.cancel(); + + // Observe the result. Note that right now the result differs depending on whether eviction occurred or + // not. The observed sequence in that case is: Group completed, Outer group by canceled., Group canceled. + // The addition of the "Group completed" is actually fine, but the fact that the cancel doesn't reach the + // source seems like a bug. Commenting out the setting of "tick" above will produce the "expected" sequence. + System.out.println(list); + assertTrue(list.contains("Source canceled")); + assertEquals(Arrays.asList( + "Group completed", // this is here when eviction occurs + "Outer group by canceled", + "Group canceled", + "Source canceled" // This is *not* here when eviction occurs + ), list); + } + + @Test + public void testCancellationOfUpstreamWhenGroupedFlowableCompletes() { + final AtomicBoolean cancelled = new AtomicBoolean(); + Flowable.just(1).repeat().doOnCancel(new Action() { + @Override + public void run() throws Exception { + cancelled.set(true); + } + }) + .groupBy(Functions.<Integer>identity(), Functions.<Integer>identity()) // + .flatMap(new Function<GroupedFlowable<Integer, Integer>, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(GroupedFlowable<Integer, Integer> g) throws Exception { + return g.first(0).toFlowable(); + } + }) + .take(4) // + .test() // + .assertComplete(); + assertTrue(cancelled.get()); + } + + //not thread safe + private static final class SingleThreadEvictingHashMap<K, V> implements Map<K, V> { + + private final List<K> list = new ArrayList<K>(); + private final Map<K, V> map = new HashMap<K, V>(); + private final int maxSize; + private final Consumer<V> evictedListener; + + SingleThreadEvictingHashMap(int maxSize, Consumer<V> evictedListener) { + this.maxSize = maxSize; + this.evictedListener = evictedListener; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + list.remove(key); + V v; + if (maxSize > 0 && list.size() == maxSize) { + //remove first + K k = list.get(0); + list.remove(0); + v = map.remove(k); + } else { + v = null; + } + list.add(key); + V result = map.put(key, value); + if (v != null) { + try { + evictedListener.accept(v); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return result; + } + + @Override + public V remove(Object key) { + list.remove(key); + return map.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + for (Entry<? extends K, ? extends V> entry: m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + list.clear(); + map.clear(); + } + + @Override + public Set<K> keySet() { + return map.keySet(); + } + + @Override + public Collection<V> values() { + return map.values(); + } + + @Override + public Set<Entry<K, V>> entrySet() { + return map.entrySet(); + } + } + + private static Function<Consumer<Object>, Map<Integer, Object>> createEvictingMapFactoryGuava(final int maxSize) { + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = // + new Function<Consumer<Object>, Map<Integer, Object>>() { + + @Override + public Map<Integer, Object> apply(final Consumer<Object> notify) throws Exception { + return CacheBuilder.newBuilder() // + .maximumSize(maxSize) // + .removalListener(new RemovalListener<Integer, Object>() { + @Override + public void onRemoval(RemovalNotification<Integer, Object> notification) { + try { + notify.accept(notification.getValue()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }}) + .<Integer, Object> build() + .asMap(); + }}; + return evictingMapFactory; + } + + private static Function<Consumer<Object>, Map<Integer, Object>> createEvictingMapFactorySynchronousOnly(final int maxSize) { + Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory = // + new Function<Consumer<Object>, Map<Integer, Object>>() { + + @Override + public Map<Integer, Object> apply(final Consumer<Object> notify) throws Exception { + return new SingleThreadEvictingHashMap<Integer, Object>(maxSize, new Consumer<Object>() { + @Override + public void accept(Object object) { + try { + notify.accept(object); + } catch (Exception e) { + throw new RuntimeException(e); + } + }}); + }}; + return evictingMapFactory; + } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final AtomicReference<QueueSubscription<GroupedFlowable<Integer, Integer>>> qs = new AtomicReference<QueueSubscription<GroupedFlowable<Integer, Integer>>>(); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + pp.groupBy(Functions.<Integer>identity(), Functions.<Integer>identity(), false, 4) + .subscribe(new FlowableSubscriber<GroupedFlowable<Integer, Integer>>() { + + boolean once; + + @Override + public void onNext(GroupedFlowable<Integer, Integer> g) { + if (!once) { + try { + GroupedFlowable<Integer, Integer> t = qs.get().poll(); + if (t != null) { + once = true; + t.subscribe(ts2); + } + } catch (Throwable ignored) { + // not relevant here + } + } + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + @SuppressWarnings("unchecked") + QueueSubscription<GroupedFlowable<Integer, Integer>> q = (QueueSubscription<GroupedFlowable<Integer, Integer>>)s; + qs.set(q); + q.requestFusion(QueueFuseable.ANY); + q.request(1); + } + }) + ; + + Runnable r1 = new Runnable() { + @Override + public void run() { + qs.get().cancel(); + qs.get().clear(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts2.cancel(); + } + }; + + for (int i = 0; i < 100; i++) { + pp.onNext(i); + } + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void fusedParallelGroupProcessing() { + Flowable.range(0, 500000) + .subscribeOn(Schedulers.single()) + .groupBy(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer i) { + return i % 2; + } + }) + .flatMap(new Function<GroupedFlowable<Integer, Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(GroupedFlowable<Integer, Integer> g) { + return g.getKey() == 0 + ? g + .parallel() + .runOn(Schedulers.computation()) + .map(Functions.<Integer>identity()) + .sequential() + : g.map(Functions.<Integer>identity()) // no need to use hide + ; + } + }) + .test() + .awaitDone(20, TimeUnit.SECONDS) + .assertValueCount(500000) + .assertComplete() + .assertNoErrors(); + } + + @Test + public void cancelledGroupResumesRequesting() { + final List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); + final AtomicInteger counter = new AtomicInteger(); + final AtomicBoolean done = new AtomicBoolean(); + Flowable.range(1, 1000) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .groupBy(Functions.justFunction(1)) + .subscribe(new Consumer<GroupedFlowable<Integer, Integer>>() { + @Override + public void accept(GroupedFlowable<Integer, Integer> v) throws Exception { + TestSubscriber<Integer> ts = TestSubscriber.create(0L); + tss.add(ts); + v.subscribe(ts); + } + }, Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + done.set(true); + } + }); + + while (!done.get()) { + tss.remove(0).cancel(); + } + + assertEquals(1000, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java index 4407dceb07..0704878805 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java @@ -15,7 +15,7 @@ */ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.*; @@ -28,14 +28,14 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableGroupJoin.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableGroupJoinTest { - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); BiFunction<Integer, Integer, Integer> add = new BiFunction<Integer, Integer, Integer>() { @Override @@ -44,20 +44,20 @@ public Integer apply(Integer t1, Integer t2) { } }; - <T> Function<Integer, Flowable<T>> just(final Flowable<T> observable) { + <T> Function<Integer, Flowable<T>> just(final Flowable<T> flowable) { return new Function<Integer, Flowable<T>>() { @Override public Flowable<T> apply(Integer t1) { - return observable; + return flowable; } }; } - <T, R> Function<T, Flowable<R>> just2(final Flowable<R> observable) { + <T, R> Function<T, Flowable<R>> just2(final Flowable<R> flowable) { return new Function<T, Flowable<R>>() { @Override public Flowable<R> apply(T t1) { - return observable; + return flowable; } }; } @@ -89,7 +89,7 @@ public void behaveAsJoin() { just(Flowable.never()), just(Flowable.never()), add2)); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source1.onNext(2); @@ -102,18 +102,18 @@ public void behaveAsJoin() { source1.onComplete(); source2.onComplete(); - verify(observer, times(1)).onNext(17); - verify(observer, times(1)).onNext(18); - verify(observer, times(1)).onNext(20); - verify(observer, times(1)).onNext(33); - verify(observer, times(1)).onNext(34); - verify(observer, times(1)).onNext(36); - verify(observer, times(1)).onNext(65); - verify(observer, times(1)).onNext(66); - verify(observer, times(1)).onNext(68); - - verify(observer, times(1)).onComplete(); //Never emitted? - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(17); + verify(subscriber, times(1)).onNext(18); + verify(subscriber, times(1)).onNext(20); + verify(subscriber, times(1)).onNext(33); + verify(subscriber, times(1)).onNext(34); + verify(subscriber, times(1)).onNext(36); + verify(subscriber, times(1)).onNext(65); + verify(subscriber, times(1)).onNext(66); + verify(subscriber, times(1)).onNext(68); + + verify(subscriber, times(1)).onComplete(); //Never emitted? + verify(subscriber, never()).onError(any(Throwable.class)); } class Person { @@ -183,19 +183,19 @@ public boolean test(PersonFruit t1) { }).subscribe(new Consumer<PersonFruit>() { @Override public void accept(PersonFruit t1) { - observer.onNext(Arrays.asList(ppf.person.name, t1.fruit)); + subscriber.onNext(Arrays.asList(ppf.person.name, t1.fruit)); } }); } @Override public void onError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - observer.onComplete(); + subscriber.onComplete(); } @Override @@ -206,12 +206,12 @@ public void onSubscribe(Subscription s) { } ); - verify(observer, times(1)).onNext(Arrays.asList("Joe", "Strawberry")); - verify(observer, times(1)).onNext(Arrays.asList("Joe", "Apple")); - verify(observer, times(1)).onNext(Arrays.asList("Charlie", "Peach")); + verify(subscriber, times(1)).onNext(Arrays.asList("Joe", "Strawberry")); + verify(subscriber, times(1)).onNext(Arrays.asList("Joe", "Apple")); + verify(subscriber, times(1)).onNext(Arrays.asList("Charlie", "Peach")); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -223,14 +223,14 @@ public void leftThrows() { just(Flowable.never()), just(Flowable.never()), add2); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); source1.onError(new RuntimeException("Forced failure")); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -242,14 +242,14 @@ public void rightThrows() { just(Flowable.never()), just(Flowable.never()), add2); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source2.onError(new RuntimeException("Forced failure")); - verify(observer, times(1)).onNext(any(Flowable.class)); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, times(1)).onNext(any(Flowable.class)); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -262,13 +262,13 @@ public void leftDurationThrows() { Flowable<Flowable<Integer>> m = source1.groupJoin(source2, just(duration1), just(Flowable.never()), add2); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -281,13 +281,13 @@ public void rightDurationThrows() { Flowable<Flowable<Integer>> m = source1.groupJoin(source2, just(Flowable.never()), just(duration1), add2); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -305,13 +305,13 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Flowable<Integer>> m = source1.groupJoin(source2, fail, just(Flowable.never()), add2); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -329,13 +329,13 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Flowable<Integer>> m = source1.groupJoin(source2, just(Flowable.never()), fail, add2); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -353,14 +353,14 @@ public Integer apply(Integer t1, Flowable<Integer> t2) { Flowable<Integer> m = source1.groupJoin(source2, just(Flowable.never()), just(Flowable.never()), fail); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source2.onNext(2); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -477,55 +477,62 @@ public Flowable<Integer> apply(Integer r, Flowable<Integer> l) throws Exception @Test public void innerErrorRight() { - Flowable.just(1) - .groupJoin( - Flowable.just(2), - new Function<Integer, Flowable<Object>>() { - @Override - public Flowable<Object> apply(Integer left) throws Exception { - return Flowable.never(); - } - }, - new Function<Integer, Flowable<Object>>() { - @Override - public Flowable<Object> apply(Integer right) throws Exception { - return Flowable.error(new TestException()); - } - }, - new BiFunction<Integer, Flowable<Integer>, Flowable<Integer>>() { - @Override - public Flowable<Integer> apply(Integer r, Flowable<Integer> l) throws Exception { - return l; + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.just(1) + .groupJoin( + Flowable.just(2), + new Function<Integer, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Integer left) throws Exception { + return Flowable.never(); + } + }, + new Function<Integer, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Integer right) throws Exception { + return Flowable.error(new TestException()); + } + }, + new BiFunction<Integer, Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Integer r, Flowable<Integer> l) throws Exception { + return l; + } } - } - ) - .flatMap(Functions.<Flowable<Integer>>identity()) - .test() - .assertFailure(TestException.class); + ) + .flatMap(Functions.<Flowable<Integer>>identity()) + .test() + .assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Object> ps1 = PublishProcessor.create(); - final PublishProcessor<Object> ps2 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Object> pp1 = PublishProcessor.create(); + final PublishProcessor<Object> pp2 = PublishProcessor.create(); List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestSubscriber<Flowable<Integer>> to = Flowable.just(1) + TestSubscriber<Flowable<Integer>> ts = Flowable.just(1) .groupJoin( Flowable.just(2).concatWith(Flowable.<Integer>never()), new Function<Integer, Flowable<Object>>() { @Override public Flowable<Object> apply(Integer left) throws Exception { - return ps1; + return pp1; } }, new Function<Integer, Flowable<Object>>() { @Override public Flowable<Object> apply(Integer right) throws Exception { - return ps2; + return pp2; } }, new BiFunction<Integer, Flowable<Integer>, Flowable<Integer>>() { @@ -543,28 +550,28 @@ public Flowable<Integer> apply(Integer r, Flowable<Integer> l) throws Exception Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex1); + pp1.onError(ex1); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex2); + pp2.onError(ex2); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertValueCount(1); + ts.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertValueCount(1); - Throwable exc = to.errors().get(0); + Throwable exc = ts.errors().get(0); if (exc instanceof CompositeException) { List<Throwable> es = TestHelper.compositeList(exc); TestHelper.assertError(es, 0, TestException.class); TestHelper.assertError(es, 1, TestException.class); } else { - to.assertError(TestException.class); + ts.assertError(TestException.class); } if (!errors.isEmpty()) { @@ -578,16 +585,16 @@ public void run() { @Test public void outerErrorRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Object> ps1 = PublishProcessor.create(); - final PublishProcessor<Object> ps2 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Object> pp1 = PublishProcessor.create(); + final PublishProcessor<Object> pp2 = PublishProcessor.create(); List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestSubscriber<Object> to = ps1 + TestSubscriber<Object> ts = pp1 .groupJoin( - ps2, + pp2, new Function<Object, Flowable<Object>>() { @Override public Flowable<Object> apply(Object left) throws Exception { @@ -616,28 +623,28 @@ public Flowable<Object> apply(Object r, Flowable<Object> l) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex1); + pp1.onError(ex1); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex2); + pp2.onError(ex2); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertNoValues(); + ts.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertNoValues(); - Throwable exc = to.errors().get(0); + Throwable exc = ts.errors().get(0); if (exc instanceof CompositeException) { List<Throwable> es = TestHelper.compositeList(exc); TestHelper.assertError(es, 0, TestException.class); TestHelper.assertError(es, 1, TestException.class); } else { - to.assertError(TestException.class); + ts.assertError(TestException.class); } if (!errors.isEmpty()) { @@ -651,12 +658,12 @@ public void run() { @Test public void rightEmission() { - final PublishProcessor<Object> ps1 = PublishProcessor.create(); - final PublishProcessor<Object> ps2 = PublishProcessor.create(); + final PublishProcessor<Object> pp1 = PublishProcessor.create(); + final PublishProcessor<Object> pp2 = PublishProcessor.create(); - TestSubscriber<Object> to = ps1 + TestSubscriber<Object> ts = pp1 .groupJoin( - ps2, + pp2, new Function<Object, Flowable<Object>>() { @Override public Flowable<Object> apply(Object left) throws Exception { @@ -679,13 +686,48 @@ public Flowable<Object> apply(Object r, Flowable<Object> l) throws Exception { .flatMap(Functions.<Flowable<Object>>identity()) .test(); - ps2.onNext(2); + pp2.onNext(2); + + pp1.onNext(1); + pp1.onComplete(); + + pp2.onComplete(); + + ts.assertResult(2); + } + + @Test + public void leftRightState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightSubscriber o = new LeftRightSubscriber(js, false); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + o.dispose(); + + assertTrue(o.isDisposed()); + + verify(js).innerValue(false, 1); + verify(js).innerValue(false, 2); + } + + @Test + public void leftRightEndState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightEndSubscriber o = new LeftRightEndSubscriber(js, false, 0); + + assertFalse(o.isDisposed()); - ps1.onNext(1); - ps1.onComplete(); + o.onNext(1); + o.onNext(2); - ps2.onComplete(); + assertTrue(o.isDisposed()); - to.assertResult(2); + verify(js).innerClose(false, o); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java new file mode 100644 index 0000000000..fbc4708710 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.processors.PublishProcessor; + +public class FlowableHideTest { + @Test + public void testHiding() { + PublishProcessor<Integer> src = PublishProcessor.create(); + + Flowable<Integer> dst = src.hide(); + + assertFalse(dst instanceof PublishProcessor); + + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + + dst.subscribe(subscriber); + + src.onNext(1); + src.onComplete(); + + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void testHidingError() { + PublishProcessor<Integer> src = PublishProcessor.create(); + + Flowable<Integer> dst = src.hide(); + + assertFalse(dst instanceof PublishProcessor); + + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + + dst.subscribe(subscriber); + + src.onError(new TestException()); + + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.hide(); + } + }); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(PublishProcessor.create().hide()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java index de62bf09ad..1f6c9e23a5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsTest.java @@ -18,12 +18,12 @@ import java.util.concurrent.atomic.*; import org.junit.Test; -import org.reactivestreams.*; +import org.reactivestreams.Subscription; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.*; @@ -144,7 +144,6 @@ public void onNext(Integer t) { assertEquals(0, count.get()); } - @Test public void testWithEmpty() { assertNull(Flowable.empty().ignoreElements().blockingGet()); @@ -174,22 +173,22 @@ public void accept(Integer t) { @Test public void testCompletedOk() { - TestObserver<Object> ts = new TestObserver<Object>(); - Flowable.range(1, 10).ignoreElements().subscribe(ts); - ts.assertNoErrors(); - ts.assertNoValues(); - ts.assertTerminated(); + TestObserver<Object> to = new TestObserver<Object>(); + Flowable.range(1, 10).ignoreElements().subscribe(to); + to.assertNoErrors(); + to.assertNoValues(); + to.assertTerminated(); } @Test public void testErrorReceived() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); TestException ex = new TestException("boo"); - Flowable.error(ex).ignoreElements().subscribe(ts); - ts.assertNoValues(); - ts.assertTerminated(); - ts.assertError(TestException.class); - ts.assertErrorMessage("boo"); + Flowable.error(ex).ignoreElements().subscribe(to); + to.assertNoValues(); + to.assertTerminated(); + to.assertError(TestException.class); + to.assertErrorMessage("boo"); } @Test @@ -252,13 +251,13 @@ public void cancel() { @Test public void fused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.just(1).hide().ignoreElements().<Integer>toFlowable() .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -325,4 +324,23 @@ public void dispose() { TestHelper.checkDisposed(Flowable.just(1).ignoreElements().toFlowable()); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.ignoreElements().toFlowable(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowableToCompletable(new Function<Flowable<Object>, Completable>() { + @Override + public Completable apply(Flowable<Object> f) + throws Exception { + return f.ignoreElements(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java index 93824d331b..541de65872 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableIntervalTest.java @@ -17,8 +17,10 @@ import org.junit.Test; -import io.reactivex.Flowable; +import io.reactivex.*; +import io.reactivex.internal.operators.flowable.FlowableInterval.IntervalSubscriber; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; public class FlowableIntervalTest { @@ -29,4 +31,22 @@ public void cancel() { .test() .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.interval(1, TimeUnit.MILLISECONDS, Schedulers.trampoline())); + } + + @Test + public void cancelledOnRun() { + TestSubscriber<Long> ts = new TestSubscriber<Long>(); + IntervalSubscriber is = new IntervalSubscriber(ts); + ts.onSubscribe(is); + + is.cancel(); + + is.run(); + + ts.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java index a1512f577b..e8f71d7aae 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java @@ -15,7 +15,6 @@ */ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -34,7 +33,7 @@ import io.reactivex.subscribers.TestSubscriber; public class FlowableJoinTest { - Subscriber<Object> observer = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); BiFunction<Integer, Integer, Integer> add = new BiFunction<Integer, Integer, Integer>() { @Override @@ -43,11 +42,11 @@ public Integer apply(Integer t1, Integer t2) { } }; - <T> Function<Integer, Flowable<T>> just(final Flowable<T> observable) { + <T> Function<Integer, Flowable<T>> just(final Flowable<T> flowable) { return new Function<Integer, Flowable<T>>() { @Override public Flowable<T> apply(Integer t1) { - return observable; + return flowable; } }; } @@ -66,7 +65,7 @@ public void normal1() { just(Flowable.never()), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source1.onNext(2); @@ -79,18 +78,18 @@ public void normal1() { source1.onComplete(); source2.onComplete(); - verify(observer, times(1)).onNext(17); - verify(observer, times(1)).onNext(18); - verify(observer, times(1)).onNext(20); - verify(observer, times(1)).onNext(33); - verify(observer, times(1)).onNext(34); - verify(observer, times(1)).onNext(36); - verify(observer, times(1)).onNext(65); - verify(observer, times(1)).onNext(66); - verify(observer, times(1)).onNext(68); - - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(17); + verify(subscriber, times(1)).onNext(18); + verify(subscriber, times(1)).onNext(20); + verify(subscriber, times(1)).onNext(33); + verify(subscriber, times(1)).onNext(34); + verify(subscriber, times(1)).onNext(36); + verify(subscriber, times(1)).onNext(65); + verify(subscriber, times(1)).onNext(66); + verify(subscriber, times(1)).onNext(68); + + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -103,7 +102,7 @@ public void normal1WithDuration() { Flowable<Integer> m = source1.join(source2, just(duration1), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source1.onNext(2); @@ -117,13 +116,13 @@ public void normal1WithDuration() { source1.onComplete(); source2.onComplete(); - verify(observer, times(1)).onNext(17); - verify(observer, times(1)).onNext(18); - verify(observer, times(1)).onNext(20); - verify(observer, times(1)).onNext(24); + verify(subscriber, times(1)).onNext(17); + verify(subscriber, times(1)).onNext(18); + verify(subscriber, times(1)).onNext(20); + verify(subscriber, times(1)).onNext(24); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -136,7 +135,7 @@ public void normal2() { just(Flowable.never()), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source1.onNext(2); @@ -148,15 +147,15 @@ public void normal2() { source2.onComplete(); - verify(observer, times(1)).onNext(17); - verify(observer, times(1)).onNext(18); - verify(observer, times(1)).onNext(33); - verify(observer, times(1)).onNext(34); - verify(observer, times(1)).onNext(65); - verify(observer, times(1)).onNext(66); + verify(subscriber, times(1)).onNext(17); + verify(subscriber, times(1)).onNext(18); + verify(subscriber, times(1)).onNext(33); + verify(subscriber, times(1)).onNext(34); + verify(subscriber, times(1)).onNext(65); + verify(subscriber, times(1)).onNext(66); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -168,14 +167,14 @@ public void leftThrows() { just(Flowable.never()), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); source1.onError(new RuntimeException("Forced failure")); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -187,14 +186,14 @@ public void rightThrows() { just(Flowable.never()), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source2.onError(new RuntimeException("Forced failure")); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -207,13 +206,13 @@ public void leftDurationThrows() { Flowable<Integer> m = source1.join(source2, just(duration1), just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -226,13 +225,13 @@ public void rightDurationThrows() { Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(duration1), add); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -250,13 +249,13 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> m = source1.join(source2, fail, just(Flowable.never()), add); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -274,13 +273,13 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> m = source1.join(source2, just(Flowable.never()), fail, add); - m.subscribe(observer); + m.subscribe(subscriber); source2.onNext(1); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -298,14 +297,14 @@ public Integer apply(Integer t1, Integer t2) { Flowable<Integer> m = source1.join(source2, just(Flowable.never()), just(Flowable.never()), fail); - m.subscribe(observer); + m.subscribe(subscriber); source1.onNext(1); source2.onNext(2); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -339,9 +338,9 @@ public Integer apply(Integer a, Integer b) throws Exception { @Test public void rightClose() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps.join(Flowable.just(2), + TestSubscriber<Integer> ts = pp.join(Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.empty()), new BiFunction<Integer, Integer, Integer>() { @@ -353,16 +352,16 @@ public Integer apply(Integer a, Integer b) throws Exception { .test() .assertEmpty(); - ps.onNext(1); + pp.onNext(1); - to.assertEmpty(); + ts.assertEmpty(); } @Test public void resultSelectorThrows2() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps.join( + TestSubscriber<Integer> ts = pp.join( Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(Flowable.never()), @@ -374,10 +373,10 @@ public Integer apply(Integer a, Integer b) throws Exception { }) .test(); - ps.onNext(1); - ps.onComplete(); + pp.onNext(1); + pp.onComplete(); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); } @Test @@ -386,10 +385,10 @@ public void badOuterSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onError(new TestException("Second")); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onError(new TestException("Second")); } } .join(Flowable.just(2), @@ -417,15 +416,15 @@ public void badEndSource() { @SuppressWarnings("rawtypes") final Subscriber[] o = { null }; - TestSubscriber<Integer> to = Flowable.just(1) + TestSubscriber<Integer> ts = Flowable.just(1) .join(Flowable.just(2), Functions.justFunction(Flowable.never()), Functions.justFunction(new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - o[0] = observer; - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + o[0] = subscriber; + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); } }), new BiFunction<Integer, Integer, Integer>() { @@ -438,7 +437,7 @@ public Integer apply(Integer a, Integer b) throws Exception { o[0].onError(new TestException("Second")); - to + ts .assertFailureAndMessage(TestException.class, "First"); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java index 21a0d2935a..517d727903 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLastTest.java @@ -53,10 +53,10 @@ public void testLastViaFlowable() { @Test public void testLast() { - Maybe<Integer> observable = Flowable.just(1, 2, 3).lastElement(); + Maybe<Integer> maybe = Flowable.just(1, 2, 3).lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(3); @@ -66,10 +66,10 @@ public void testLast() { @Test public void testLastWithOneElement() { - Maybe<Integer> observable = Flowable.just(1).lastElement(); + Maybe<Integer> maybe = Flowable.just(1).lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -79,10 +79,10 @@ public void testLastWithOneElement() { @Test public void testLastWithEmpty() { - Maybe<Integer> observable = Flowable.<Integer> empty().lastElement(); + Maybe<Integer> maybe = Flowable.<Integer> empty().lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer).onComplete(); @@ -92,7 +92,7 @@ public void testLastWithEmpty() { @Test public void testLastWithPredicate() { - Maybe<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Maybe<Integer> maybe = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override @@ -103,7 +103,7 @@ public boolean test(Integer t1) { .lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(6); @@ -113,7 +113,7 @@ public boolean test(Integer t1) { @Test public void testLastWithPredicateAndOneElement() { - Maybe<Integer> observable = Flowable.just(1, 2) + Maybe<Integer> maybe = Flowable.just(1, 2) .filter( new Predicate<Integer>() { @@ -125,7 +125,7 @@ public boolean test(Integer t1) { .lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -135,7 +135,7 @@ public boolean test(Integer t1) { @Test public void testLastWithPredicateAndEmpty() { - Maybe<Integer> observable = Flowable.just(1) + Maybe<Integer> maybe = Flowable.just(1) .filter( new Predicate<Integer>() { @@ -146,7 +146,7 @@ public boolean test(Integer t1) { }).lastElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer).onComplete(); @@ -156,11 +156,11 @@ public boolean test(Integer t1) { @Test public void testLastOrDefault() { - Single<Integer> observable = Flowable.just(1, 2, 3) + Single<Integer> single = Flowable.just(1, 2, 3) .last(4); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(3); @@ -170,10 +170,10 @@ public void testLastOrDefault() { @Test public void testLastOrDefaultWithOneElement() { - Single<Integer> observable = Flowable.just(1).last(2); + Single<Integer> single = Flowable.just(1).last(2); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -183,11 +183,11 @@ public void testLastOrDefaultWithOneElement() { @Test public void testLastOrDefaultWithEmpty() { - Single<Integer> observable = Flowable.<Integer> empty() + Single<Integer> single = Flowable.<Integer> empty() .last(1); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -197,7 +197,7 @@ public void testLastOrDefaultWithEmpty() { @Test public void testLastOrDefaultWithPredicate() { - Single<Integer> observable = Flowable.just(1, 2, 3, 4, 5, 6) + Single<Integer> single = Flowable.just(1, 2, 3, 4, 5, 6) .filter(new Predicate<Integer>() { @Override @@ -208,7 +208,7 @@ public boolean test(Integer t1) { .last(8); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(6); @@ -218,7 +218,7 @@ public boolean test(Integer t1) { @Test public void testLastOrDefaultWithPredicateAndOneElement() { - Single<Integer> observable = Flowable.just(1, 2) + Single<Integer> single = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override @@ -229,7 +229,7 @@ public boolean test(Integer t1) { .last(4); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -239,7 +239,7 @@ public boolean test(Integer t1) { @Test public void testLastOrDefaultWithPredicateAndEmpty() { - Single<Integer> observable = Flowable.just(1) + Single<Integer> single = Flowable.just(1) .filter( new Predicate<Integer>() { @@ -251,7 +251,7 @@ public boolean test(Integer t1) { .last(2); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -312,40 +312,40 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowableToMaybe(new Function<Flowable<Object>, MaybeSource<Object>>() { @Override - public MaybeSource<Object> apply(Flowable<Object> o) throws Exception { - return o.lastElement(); + public MaybeSource<Object> apply(Flowable<Object> f) throws Exception { + return f.lastElement(); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.lastElement().toFlowable(); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.lastElement().toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, SingleSource<Object>>() { @Override - public SingleSource<Object> apply(Flowable<Object> o) throws Exception { - return o.lastOrError(); + public SingleSource<Object> apply(Flowable<Object> f) throws Exception { + return f.lastOrError(); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.lastOrError().toFlowable(); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.lastOrError().toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, SingleSource<Object>>() { @Override - public SingleSource<Object> apply(Flowable<Object> o) throws Exception { - return o.last(2); + public SingleSource<Object> apply(Flowable<Object> f) throws Exception { + return f.last(2); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.last(2).toFlowable(); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.last(2).toFlowable(); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLiftTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLiftTest.java index 84fe5967a7..bb28c6c597 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLiftTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLiftTest.java @@ -15,21 +15,25 @@ import static org.junit.Assert.*; +import java.util.List; + import org.junit.Test; import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.plugins.RxJavaPlugins; public class FlowableLiftTest { @Test public void callbackCrash() { + List<Throwable> errors = TestHelper.trackPluginErrors(); try { Flowable.just(1) .lift(new FlowableOperator<Object, Integer>() { @Override - public Subscriber<? super Integer> apply(Subscriber<? super Object> o) throws Exception { + public Subscriber<? super Integer> apply(Subscriber<? super Object> subscriber) throws Exception { throw new TestException(); } }) @@ -37,6 +41,9 @@ public Subscriber<? super Integer> apply(Subscriber<? super Object> o) throws Ex fail("Should have thrown"); } catch (NullPointerException ex) { assertTrue(ex.toString(), ex.getCause() instanceof TestException); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableLimitTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLimitTest.java new file mode 100644 index 0000000000..9150bab4ad --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableLimitTest.java @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableLimitTest implements LongConsumer, Action { + + final List<Long> requests = new ArrayList<Long>(); + + static final Long CANCELLED = -100L; + + @Override + public void accept(long t) throws Exception { + requests.add(t); + } + + @Override + public void run() throws Exception { + requests.add(CANCELLED); + } + + @Test + public void shorterSequence() { + Flowable.range(1, 5) + .doOnRequest(this) + .limit(6) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(6, requests.get(0).intValue()); + } + + @Test + public void exactSequence() { + Flowable.range(1, 5) + .doOnRequest(this) + .doOnCancel(this) + .limit(5) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(2, requests.size()); + assertEquals(5, requests.get(0).intValue()); + assertEquals(CANCELLED, requests.get(1)); + } + + @Test + public void longerSequence() { + Flowable.range(1, 6) + .doOnRequest(this) + .limit(5) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(5, requests.get(0).intValue()); + } + + @Test + public void error() { + Flowable.error(new TestException()) + .limit(5) + .test() + .assertFailure(TestException.class); + } + + @Test + public void limitZero() { + Flowable.range(1, 5) + .doOnCancel(this) + .doOnRequest(this) + .limit(0) + .test() + .assertResult(); + + assertEquals(1, requests.size()); + assertEquals(CANCELLED, requests.get(0)); + } + + @Test + public void limitStep() { + TestSubscriber<Integer> ts = Flowable.range(1, 6) + .doOnRequest(this) + .limit(5) + .test(0L); + + assertEquals(0, requests.size()); + + ts.request(1); + ts.assertValue(1); + + ts.request(2); + ts.assertValues(1, 2, 3); + + ts.request(3); + ts.assertResult(1, 2, 3, 4, 5); + + assertEquals(Arrays.asList(1L, 2L, 2L), requests); + } + + @Test + public void limitAndTake() { + Flowable.range(1, 5) + .doOnCancel(this) + .doOnRequest(this) + .limit(6) + .take(5) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(Arrays.asList(6L, CANCELLED), requests); + } + + @Test + public void noOverrequest() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp + .doOnRequest(this) + .limit(5) + .test(0L); + + ts.request(5); + ts.request(10); + + assertTrue(pp.offer(1)); + pp.onComplete(); + + ts.assertResult(1); + } + + @Test + public void cancelIgnored() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + BooleanSubscription bs = new BooleanSubscription(); + s.onSubscribe(bs); + + assertTrue(bs.isCancelled()); + + s.onNext(1); + s.onComplete(); + s.onError(new TestException()); + + s.onSubscribe(null); + } + } + .limit(0) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + TestHelper.assertError(errors, 1, NullPointerException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.range(1, 5).limit(3)); + } + + @Test + public void requestRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestSubscriber<Integer> ts = Flowable.range(1, 10) + .limit(5) + .test(0L); + + Runnable r = new Runnable() { + @Override + public void run() { + ts.request(3); + } + }; + + TestHelper.race(r, r); + + ts.assertResult(1, 2, 3, 4, 5); + } + } + + @Test + public void errorAfterLimitReached() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.error(new TestException()) + .limit(0) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapNotificationTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapNotificationTest.java index 287b83fb59..1346e7bc50 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapNotificationTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapNotificationTest.java @@ -104,9 +104,9 @@ public Integer call() { public void noBackpressure() { TestSubscriber<Object> ts = TestSubscriber.create(0L); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - new FlowableMapNotification<Integer, Integer>(ps, + new FlowableMapNotification<Integer, Integer>(pp, new Function<Integer, Integer>() { @Override public Integer apply(Integer item) { @@ -131,10 +131,10 @@ public Integer call() { ts.assertNoErrors(); ts.assertNotComplete(); - ps.onNext(1); - ps.onNext(2); - ps.onNext(3); - ps.onComplete(); + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onComplete(); ts.assertNoValues(); ts.assertNoErrors(); @@ -153,9 +153,9 @@ public void dispose() { TestHelper.checkDisposed(new Flowable<Integer>() { @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { + protected void subscribeActual(Subscriber<? super Integer> subscriber) { MapNotificationSubscriber mn = new MapNotificationSubscriber( - observer, + subscriber, Functions.justFunction(Flowable.just(1)), Functions.justFunction(Flowable.just(2)), Functions.justCallable(Flowable.just(3)) @@ -169,8 +169,8 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Object> o) throws Exception { - return o.flatMap( + public Flowable<Integer> apply(Flowable<Object> f) throws Exception { + return f.flatMap( Functions.justFunction(Flowable.just(1)), Functions.justFunction(Flowable.just(2)), Functions.justCallable(Flowable.just(3)) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java index e74f5bc961..0ff960c4cc 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -24,7 +23,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; @@ -57,9 +55,9 @@ public void before() { public void testMap() { Map<String, String> m1 = getMap("One"); Map<String, String> m2 = getMap("Two"); - Flowable<Map<String, String>> observable = Flowable.just(m1, m2); + Flowable<Map<String, String>> flowable = Flowable.just(m1, m2); - Flowable<String> m = observable.map(new Function<Map<String, String>, String>() { + Flowable<String> m = flowable.map(new Function<Map<String, String>, String>() { @Override public String apply(Map<String, String> map) { return map.get("firstName"); @@ -120,19 +118,19 @@ public String apply(Map<String, String> map) { public void testMapMany2() { Map<String, String> m1 = getMap("One"); Map<String, String> m2 = getMap("Two"); - Flowable<Map<String, String>> observable1 = Flowable.just(m1, m2); + Flowable<Map<String, String>> flowable1 = Flowable.just(m1, m2); Map<String, String> m3 = getMap("Three"); Map<String, String> m4 = getMap("Four"); - Flowable<Map<String, String>> observable2 = Flowable.just(m3, m4); + Flowable<Map<String, String>> flowable2 = Flowable.just(m3, m4); - Flowable<Flowable<Map<String, String>>> observable = Flowable.just(observable1, observable2); + Flowable<Flowable<Map<String, String>>> f = Flowable.just(flowable1, flowable2); - Flowable<String> m = observable.flatMap(new Function<Flowable<Map<String, String>>, Flowable<String>>() { + Flowable<String> m = f.flatMap(new Function<Flowable<Map<String, String>>, Flowable<String>>() { @Override - public Flowable<String> apply(Flowable<Map<String, String>> o) { - return o.map(new Function<Map<String, String>, String>() { + public Flowable<String> apply(Flowable<Map<String, String>> f) { + return f.map(new Function<Map<String, String>, String>() { @Override public String apply(Map<String, String> map) { @@ -155,12 +153,14 @@ public String apply(Map<String, String> map) { @Test public void testMapWithError() { + final List<Throwable> errors = new ArrayList<Throwable>(); + Flowable<String> w = Flowable.just("one", "fail", "two", "three", "fail"); Flowable<String> m = w.map(new Function<String, String>() { @Override public String apply(String s) { if ("fail".equals(s)) { - throw new RuntimeException("Forced Failure"); + throw new TestException("Forced Failure"); } return s; } @@ -168,7 +168,7 @@ public String apply(String s) { @Override public void accept(Throwable t1) { - t1.printStackTrace(); + errors.add(t1); } }); @@ -178,7 +178,9 @@ public void accept(Throwable t1) { verify(stringSubscriber, never()).onNext("two"); verify(stringSubscriber, never()).onNext("three"); verify(stringSubscriber, never()).onComplete(); - verify(stringSubscriber, times(1)).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onError(any(TestException.class)); + + TestHelper.assertError(errors, 0, TestException.class, "Forced Failure"); } @Test(expected = IllegalArgumentException.class) @@ -291,11 +293,11 @@ public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // Flowable.OnSubscribe<Object> creator = new Flowable.OnSubscribe<Object>() { // // @Override -// public void call(Subscriber<? super Object> observer) { -// observer.onNext("a"); -// observer.onNext("b"); -// observer.onNext("c"); -// observer.onComplete(); +// public void call(Subscriber<? super Object> subscriber) { +// subscriber.onNext("a"); +// subscriber.onNext("b"); +// subscriber.onNext("c"); +// subscriber.onComplete(); // } // }; // @@ -339,22 +341,22 @@ public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { @Test public void functionCrashUnsubscribes() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - ps.map(new Function<Integer, Integer>() { + pp.map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) { throw new TestException(); } }).subscribe(ts); - Assert.assertTrue("Not subscribed?", ps.hasSubscribers()); + Assert.assertTrue("Not subscribed?", pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - Assert.assertFalse("Subscribed?", ps.hasSubscribers()); + Assert.assertFalse("Subscribed?", pp.hasSubscribers()); ts.assertError(TestException.class); } @@ -418,7 +420,7 @@ public boolean test(Integer v) throws Exception { @Test public void mapFilterFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 2) .map(new Function<Integer, Integer>() { @@ -436,13 +438,13 @@ public boolean test(Integer v) throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(2, 3); } @Test public void mapFilterFusedHidden() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 2).hide() .map(new Function<Integer, Integer>() { @@ -460,7 +462,7 @@ public boolean test(Integer v) throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(2, 3); } @@ -496,7 +498,7 @@ public Object apply(Integer v) throws Exception { @Test public void mapFilterMapperCrashFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 2).hide() .map(new Function<Integer, Integer>() { @@ -514,7 +516,7 @@ public boolean test(Integer v) throws Exception { .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertFailure(TestException.class); } @@ -556,7 +558,7 @@ public boolean test(Integer v) throws Exception { @Test public void mapFilterFused2() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -580,7 +582,7 @@ public boolean test(Integer v) throws Exception { up.onComplete(); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(2, 3); } @@ -630,49 +632,49 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.map(Functions.identity()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.map(Functions.identity()); } }); } @Test public void fusedSync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .map(Functions.<Integer>identity()) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedAsync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); us .map(Functions.<Integer>identity()) - .subscribe(to); + .subscribe(ts); TestHelper.emit(us, 1, 2, 3, 4, 5); - SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedReject() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY | QueueFuseable.BOUNDARY); Flowable.range(1, 5) .map(Functions.<Integer>identity()) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); } @@ -680,8 +682,8 @@ public void fusedReject() { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.map(Functions.identity()); + public Object apply(Flowable<Object> f) throws Exception { + return f.map(Functions.identity()); } }, false, 1, 1, 1); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMaterializeTest.java index e1d3045ea6..b35006ebbf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMaterializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMaterializeTest.java @@ -233,8 +233,8 @@ private static class TestAsyncErrorObservable implements Publisher<String> { volatile Thread t; @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override @@ -247,14 +247,14 @@ public void run() { } catch (Throwable e) { } - observer.onError(new NullPointerException()); + subscriber.onError(new NullPointerException()); return; } else { - observer.onNext(s); + subscriber.onNext(s); } } System.out.println("subscription complete"); - observer.onComplete(); + subscriber.onComplete(); } }); @@ -290,8 +290,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Notification<Object>>>() { @Override - public Flowable<Notification<Object>> apply(Flowable<Object> o) throws Exception { - return o.materialize(); + public Flowable<Notification<Object>> apply(Flowable<Object> f) throws Exception { + return f.materialize(); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java index d0f3f81a84..671abc314e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java @@ -25,7 +25,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.functions.LongConsumer; import io.reactivex.internal.subscriptions.BooleanSubscription; @@ -34,164 +33,164 @@ public class FlowableMergeDelayErrorTest { - Subscriber<String> stringObserver; + Subscriber<String> stringSubscriber; @Before public void before() { - stringObserver = TestHelper.mockSubscriber(); + stringSubscriber = TestHelper.mockSubscriber(); } @Test public void testErrorDelayed1() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); - - Flowable<String> m = Flowable.mergeDelayError(o1, o2); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); + + Flowable<String> m = Flowable.mergeDelayError(f1, f2); + m.subscribe(stringSubscriber); + + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(0)).onNext("five"); // despite not expecting it ... we don't do anything to prevent it if the source Flowable keeps sending after onError // inner Flowable errors are considered terminal for that source -// verify(stringObserver, times(1)).onNext("six"); +// verify(stringSubscriber, times(1)).onNext("six"); // inner Flowable errors are considered terminal for that source } @Test public void testErrorDelayed2() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Flowable<String> o3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); - final Flowable<String> o4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); - - Flowable<String> m = Flowable.mergeDelayError(o1, o2, o3, o4); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(CompositeException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Flowable<String> f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); + final Flowable<String> f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); + + Flowable<String> m = Flowable.mergeDelayError(f1, f2, f3, f4); + m.subscribe(stringSubscriber); + + verify(stringSubscriber, times(1)).onError(any(CompositeException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(0)).onNext("five"); // despite not expecting it ... we don't do anything to prevent it if the source Flowable keeps sending after onError // inner Flowable errors are considered terminal for that source -// verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); +// verify(stringSubscriber, times(1)).onNext("six"); + verify(stringSubscriber, times(1)).onNext("seven"); + verify(stringSubscriber, times(1)).onNext("eight"); + verify(stringSubscriber, times(1)).onNext("nine"); } @Test public void testErrorDelayed3() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("four", "five", "six")); - final Flowable<String> o3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); - final Flowable<String> o4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("four", "five", "six")); + final Flowable<String> f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); + final Flowable<String> f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); - Flowable<String> m = Flowable.mergeDelayError(o1, o2, o3, o4); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(f1, f2, f3, f4); + m.subscribe(stringSubscriber); - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(1)).onNext("five"); + verify(stringSubscriber, times(1)).onNext("six"); + verify(stringSubscriber, times(1)).onNext("seven"); + verify(stringSubscriber, times(1)).onNext("eight"); + verify(stringSubscriber, times(1)).onNext("nine"); } @Test public void testErrorDelayed4() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("four", "five", "six")); - final Flowable<String> o3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight")); - final Flowable<String> o4 = Flowable.unsafeCreate(new TestErrorFlowable("nine", null)); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("four", "five", "six")); + final Flowable<String> f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight")); + final Flowable<String> f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine", null)); - Flowable<String> m = Flowable.mergeDelayError(o1, o2, o3, o4); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(f1, f2, f3, f4); + m.subscribe(stringSubscriber); - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(1)).onNext("five"); + verify(stringSubscriber, times(1)).onNext("six"); + verify(stringSubscriber, times(1)).onNext("seven"); + verify(stringSubscriber, times(1)).onNext("eight"); + verify(stringSubscriber, times(1)).onNext("nine"); } @Test public void testErrorDelayed4WithThreading() { - final TestAsyncErrorFlowable o1 = new TestAsyncErrorFlowable("one", "two", "three"); - final TestAsyncErrorFlowable o2 = new TestAsyncErrorFlowable("four", "five", "six"); - final TestAsyncErrorFlowable o3 = new TestAsyncErrorFlowable("seven", "eight"); + final TestAsyncErrorFlowable f1 = new TestAsyncErrorFlowable("one", "two", "three"); + final TestAsyncErrorFlowable f2 = new TestAsyncErrorFlowable("four", "five", "six"); + final TestAsyncErrorFlowable f3 = new TestAsyncErrorFlowable("seven", "eight"); // throw the error at the very end so no onComplete will be called after it - final TestAsyncErrorFlowable o4 = new TestAsyncErrorFlowable("nine", null); + final TestAsyncErrorFlowable f4 = new TestAsyncErrorFlowable("nine", null); - Flowable<String> m = Flowable.mergeDelayError(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2), Flowable.unsafeCreate(o3), Flowable.unsafeCreate(o4)); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(Flowable.unsafeCreate(f1), Flowable.unsafeCreate(f2), Flowable.unsafeCreate(f3), Flowable.unsafeCreate(f4)); + m.subscribe(stringSubscriber); try { - o1.t.join(); - o2.t.join(); - o3.t.join(); - o4.t.join(); + f1.t.join(); + f2.t.join(); + f3.t.join(); + f4.t.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(1)).onNext("five"); + verify(stringSubscriber, times(1)).onNext("six"); + verify(stringSubscriber, times(1)).onNext("seven"); + verify(stringSubscriber, times(1)).onNext("eight"); + verify(stringSubscriber, times(1)).onNext("nine"); + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); } @Test public void testCompositeErrorDelayed1() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", null)); - - Flowable<String> m = Flowable.mergeDelayError(o1, o2); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(Throwable.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(0)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", null)); + + Flowable<String> m = Flowable.mergeDelayError(f1, f2); + m.subscribe(stringSubscriber); + + verify(stringSubscriber, times(1)).onError(any(Throwable.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(0)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(0)).onNext("five"); // despite not expecting it ... we don't do anything to prevent it if the source Flowable keeps sending after onError // inner Flowable errors are considered terminal for that source -// verify(stringObserver, times(1)).onNext("six"); +// verify(stringSubscriber, times(1)).onNext("six"); } @Test public void testCompositeErrorDelayed2() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", null)); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", null)); - Flowable<String> m = Flowable.mergeDelayError(o1, o2); + Flowable<String> m = Flowable.mergeDelayError(f1, f2); CaptureObserver w = new CaptureObserver(); m.subscribe(w); @@ -218,84 +217,84 @@ public void testCompositeErrorDelayed2() { @Test public void testMergeFlowableOfFlowables() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable<Flowable<String>> FlowableOfFlowables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { + Flowable<Flowable<String>> flowableOfFlowables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // simulate what would happen in a Flowable - observer.onNext(o1); - observer.onNext(o2); - observer.onComplete(); + subscriber.onNext(f1); + subscriber.onNext(f2); + subscriber.onComplete(); } }); - Flowable<String> m = Flowable.mergeDelayError(FlowableOfFlowables); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(flowableOfFlowables); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onComplete(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); } @Test public void testMergeArray() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable<String> m = Flowable.mergeDelayError(o1, o2); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(f1, f2); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(2)).onNext("hello"); + verify(stringSubscriber, times(1)).onComplete(); } @Test public void testMergeList() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); List<Flowable<String>> listOfFlowables = new ArrayList<Flowable<String>>(); - listOfFlowables.add(o1); - listOfFlowables.add(o2); + listOfFlowables.add(f1); + listOfFlowables.add(f2); Flowable<String> m = Flowable.mergeDelayError(Flowable.fromIterable(listOfFlowables)); - m.subscribe(stringObserver); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onComplete(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); } @Test public void testMergeArrayWithThreading() { - final TestASynchronousFlowable o1 = new TestASynchronousFlowable(); - final TestASynchronousFlowable o2 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f1 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f2 = new TestASynchronousFlowable(); - Flowable<String> m = Flowable.mergeDelayError(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.mergeDelayError(Flowable.unsafeCreate(f1), Flowable.unsafeCreate(f2)); + m.subscribe(stringSubscriber); try { - o1.t.join(); - o2.t.join(); + f1.t.join(); + f2.t.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(2)).onNext("hello"); + verify(stringSubscriber, times(1)).onComplete(); } @Test(timeout = 1000L) public void testSynchronousError() { - final Flowable<Flowable<String>> o1 = Flowable.error(new RuntimeException("unit test")); + final Flowable<Flowable<String>> f1 = Flowable.error(new RuntimeException("unit test")); final CountDownLatch latch = new CountDownLatch(1); - Flowable.mergeDelayError(o1).subscribe(new DefaultSubscriber<String>() { + Flowable.mergeDelayError(f1).subscribe(new DefaultSubscriber<String>() { @Override public void onComplete() { fail("Expected onError path"); @@ -322,10 +321,10 @@ public void onNext(String s) { private static class TestSynchronousFlowable implements Publisher<String> { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("hello"); - observer.onComplete(); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("hello"); + subscriber.onComplete(); } } @@ -333,14 +332,14 @@ private static class TestASynchronousFlowable implements Publisher<String> { Thread t; @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override public void run() { - observer.onNext("hello"); - observer.onComplete(); + subscriber.onNext("hello"); + subscriber.onComplete(); } }); @@ -357,22 +356,22 @@ private static class TestErrorFlowable implements Publisher<String> { } @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); boolean errorThrown = false; for (String s : valuesToReturn) { if (s == null) { System.out.println("throwing exception"); - observer.onError(new NullPointerException()); + subscriber.onError(new NullPointerException()); errorThrown = true; // purposefully not returning here so it will continue calling onNext // so that we also test that we handle bad sequences like this } else { - observer.onNext(s); + subscriber.onNext(s); } } if (!errorThrown) { - observer.onComplete(); + subscriber.onComplete(); } } } @@ -388,8 +387,8 @@ private static class TestAsyncErrorFlowable implements Publisher<String> { Thread t; @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override @@ -402,14 +401,14 @@ public void run() { } catch (Throwable e) { } - observer.onError(new NullPointerException()); + subscriber.onError(new NullPointerException()); return; } else { - observer.onNext(s); + subscriber.onNext(s); } } System.out.println("subscription complete"); - observer.onComplete(); + subscriber.onComplete(); } }); @@ -436,6 +435,7 @@ public void onNext(String args) { } } + @Test @Ignore("Subscribers should not throw") public void testMergeSourceWhichDoesntPropagateExceptionBack() { @@ -455,8 +455,8 @@ public void subscribe(Subscriber<? super Integer> t1) { Flowable<Integer> result = Flowable.mergeDelayError(source, Flowable.just(2)); - final Subscriber<Integer> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); result.subscribe(new DefaultSubscriber<Integer>() { int calls; @@ -465,17 +465,17 @@ public void onNext(Integer t) { if (calls++ == 0) { throw new TestException(); } - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); @@ -484,12 +484,12 @@ public void onComplete() { * If the child onNext throws, why would we keep accepting values from * other sources? */ - inOrder.verify(o).onNext(2); - inOrder.verify(o, never()).onNext(0); - inOrder.verify(o, never()).onNext(1); - inOrder.verify(o, never()).onNext(anyInt()); - inOrder.verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber, never()).onNext(0); + inOrder.verify(subscriber, never()).onNext(1); + inOrder.verify(subscriber, never()).onNext(anyInt()); + inOrder.verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -509,30 +509,30 @@ public void testErrorInParentFlowable() { @Test public void testErrorInParentFlowableDelayed() throws Exception { for (int i = 0; i < 50; i++) { - final TestASynchronous1sDelayedFlowable o1 = new TestASynchronous1sDelayedFlowable(); - final TestASynchronous1sDelayedFlowable o2 = new TestASynchronous1sDelayedFlowable(); + final TestASynchronous1sDelayedFlowable f1 = new TestASynchronous1sDelayedFlowable(); + final TestASynchronous1sDelayedFlowable f2 = new TestASynchronous1sDelayedFlowable(); Flowable<Flowable<String>> parentFlowable = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override public void subscribe(Subscriber<? super Flowable<String>> op) { op.onSubscribe(new BooleanSubscription()); - op.onNext(Flowable.unsafeCreate(o1)); - op.onNext(Flowable.unsafeCreate(o2)); + op.onNext(Flowable.unsafeCreate(f1)); + op.onNext(Flowable.unsafeCreate(f2)); op.onError(new NullPointerException("throwing exception in parent")); } }); - Subscriber<String> stringObserver = TestHelper.mockSubscriber(); + stringSubscriber = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(stringObserver); + TestSubscriber<String> ts = new TestSubscriber<String>(stringSubscriber); Flowable<String> m = Flowable.mergeDelayError(parentFlowable); m.subscribe(ts); System.out.println("testErrorInParentFlowableDelayed | " + i); ts.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS); ts.assertTerminated(); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); } } @@ -540,8 +540,8 @@ private static class TestASynchronous1sDelayedFlowable implements Publisher<Stri Thread t; @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override @@ -549,16 +549,17 @@ public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { - observer.onError(e); + subscriber.onError(e); } - observer.onNext("hello"); - observer.onComplete(); + subscriber.onNext("hello"); + subscriber.onComplete(); } }); t.start(); } } + @Test public void testDelayErrorMaxConcurrent() { final List<Long> requests = new ArrayList<Long>(); @@ -585,18 +586,18 @@ public void accept(long t1) { // This is pretty much a clone of testMergeList but with the overloaded MergeDelayError for Iterables @Test public void mergeIterable() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); List<Flowable<String>> listOfFlowables = new ArrayList<Flowable<String>>(); - listOfFlowables.add(o1); - listOfFlowables.add(o2); + listOfFlowables.add(f1); + listOfFlowables.add(f2); Flowable<String> m = Flowable.mergeDelayError(listOfFlowables); - m.subscribe(stringObserver); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onComplete(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); } @SuppressWarnings("unchecked") @@ -604,22 +605,22 @@ public void mergeIterable() { public void iterableMaxConcurrent() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps1 = PublishProcessor.create(); - PublishProcessor<Integer> ps2 = PublishProcessor.create(); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); - Flowable.mergeDelayError(Arrays.asList(ps1, ps2), 1).subscribe(ts); + Flowable.mergeDelayError(Arrays.asList(pp1, pp2), 1).subscribe(ts); - assertTrue("ps1 has no subscribers?!", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?!", ps2.hasSubscribers()); + assertTrue("ps1 has no subscribers?!", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?!", pp2.hasSubscribers()); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - assertFalse("ps1 has subscribers?!", ps1.hasSubscribers()); - assertTrue("ps2 has no subscribers?!", ps2.hasSubscribers()); + assertFalse("ps1 has subscribers?!", pp1.hasSubscribers()); + assertTrue("ps2 has no subscribers?!", pp2.hasSubscribers()); - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); ts.assertValues(1, 2); ts.assertNoErrors(); @@ -631,22 +632,22 @@ public void iterableMaxConcurrent() { public void iterableMaxConcurrentError() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps1 = PublishProcessor.create(); - PublishProcessor<Integer> ps2 = PublishProcessor.create(); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); - Flowable.mergeDelayError(Arrays.asList(ps1, ps2), 1).subscribe(ts); + Flowable.mergeDelayError(Arrays.asList(pp1, pp2), 1).subscribe(ts); - assertTrue("ps1 has no subscribers?!", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?!", ps2.hasSubscribers()); + assertTrue("ps1 has no subscribers?!", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?!", pp2.hasSubscribers()); - ps1.onNext(1); - ps1.onError(new TestException()); + pp1.onNext(1); + pp1.onError(new TestException()); - assertFalse("ps1 has subscribers?!", ps1.hasSubscribers()); - assertTrue("ps2 has no subscribers?!", ps2.hasSubscribers()); + assertFalse("ps1 has subscribers?!", pp1.hasSubscribers()); + assertTrue("ps2 has no subscribers?!", pp2.hasSubscribers()); - ps2.onNext(2); - ps2.onError(new TestException()); + pp2.onNext(2); + pp2.onError(new TestException()); ts.assertValues(1, 2); ts.assertError(CompositeException.class); @@ -737,7 +738,6 @@ public void array() { } } - @SuppressWarnings("unchecked") @Test public void mergeArrayDelayError() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java index 0908334c3f..ef5dd519ff 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeMaxConcurrentTest.java @@ -30,13 +30,6 @@ public class FlowableMergeMaxConcurrentTest { - Subscriber<String> stringObserver; - - @Before - public void before() { - stringObserver = TestHelper.mockSubscriber(); - } - @Test public void testWhenMaxConcurrentIsOne() { for (int i = 0; i < 100; i++) { @@ -136,6 +129,7 @@ public void testMergeALotOfSourcesOneByOneSynchronously() { } assertEquals(j, n); } + @Test public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { int n = 10000; @@ -170,6 +164,7 @@ public void testSimple() { ts.assertValueSequence(result); } } + @Test public void testSimpleOneLess() { for (int i = 2; i < 100; i++) { @@ -188,6 +183,7 @@ public void testSimpleOneLess() { ts.assertValueSequence(result); } } + @Test//(timeout = 20000) public void testSimpleAsyncLoop() { IoScheduler ios = (IoScheduler)Schedulers.io(); @@ -200,6 +196,7 @@ public void testSimpleAsyncLoop() { } } } + @Test(timeout = 10000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { @@ -220,12 +217,14 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } + @Test(timeout = 10000) public void testSimpleOneLessAsyncLoop() { for (int i = 0; i < 200; i++) { testSimpleOneLessAsync(); } } + @Test(timeout = 10000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); @@ -250,6 +249,7 @@ public void testSimpleOneLessAsync() { assertEquals(expected, actual); } } + @Test(timeout = 5000) public void testBackpressureHonored() throws Exception { List<Flowable<Integer>> sourceList = new ArrayList<Flowable<Integer>>(3); @@ -280,6 +280,7 @@ public void onNext(Integer t) { ts.dispose(); } + @Test(timeout = 5000) public void testTake() throws Exception { List<Flowable<Integer>> sourceList = new ArrayList<Flowable<Integer>>(3); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java index b095878415..34a732be7c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java @@ -15,7 +15,6 @@ import static java.util.Arrays.asList; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -40,13 +39,13 @@ public class FlowableMergeTest { - Subscriber<String> stringObserver; + Subscriber<String> stringSubscriber; int count; @Before public void before() { - stringObserver = TestHelper.mockSubscriber(); + stringSubscriber = TestHelper.mockSubscriber(); for (Thread t : Thread.getAllStackTraces().keySet()) { if (t.getName().startsWith("RxNewThread")) { @@ -75,56 +74,56 @@ public void after() { @Test public void testMergeFlowableOfFlowables() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable<Flowable<String>> FlowableOfFlowables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { + Flowable<Flowable<String>> flowableOfFlowables = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); // simulate what would happen in a Flowable - observer.onNext(o1); - observer.onNext(o2); - observer.onComplete(); + subscriber.onNext(f1); + subscriber.onNext(f2); + subscriber.onComplete(); } }); - Flowable<String> m = Flowable.merge(FlowableOfFlowables); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.merge(flowableOfFlowables); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onComplete(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); } @Test public void testMergeArray() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - Flowable<String> m = Flowable.merge(o1, o2); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.merge(f1, f2); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(2)).onNext("hello"); + verify(stringSubscriber, times(1)).onComplete(); } @Test public void testMergeList() { - final Flowable<String> o1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestSynchronousFlowable()); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestSynchronousFlowable()); List<Flowable<String>> listOfFlowables = new ArrayList<Flowable<String>>(); - listOfFlowables.add(o1); - listOfFlowables.add(o2); + listOfFlowables.add(f1); + listOfFlowables.add(f2); Flowable<String> m = Flowable.merge(listOfFlowables); - m.subscribe(stringObserver); + m.subscribe(stringSubscriber); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onComplete(); - verify(stringObserver, times(2)).onNext("hello"); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(1)).onComplete(); + verify(stringSubscriber, times(2)).onNext("hello"); } @Test(timeout = 1000) @@ -136,7 +135,7 @@ public void testUnSubscribeFlowableOfFlowables() throws InterruptedException { Flowable<Flowable<Long>> source = Flowable.unsafeCreate(new Publisher<Flowable<Long>>() { @Override - public void subscribe(final Subscriber<? super Flowable<Long>> observer) { + public void subscribe(final Subscriber<? super Flowable<Long>> subscriber) { // verbose on purpose so I can track the inside of it final Subscription s = new Subscription() { @@ -152,7 +151,7 @@ public void cancel() { } }; - observer.onSubscribe(s); + subscriber.onSubscribe(s); new Thread(new Runnable() { @@ -160,10 +159,10 @@ public void cancel() { public void run() { while (!unsubscribed.get()) { - observer.onNext(Flowable.just(1L, 2L)); + subscriber.onNext(Flowable.just(1L, 2L)); } System.out.println("Done looping after unsubscribe: " + unsubscribed.get()); - observer.onComplete(); + subscriber.onComplete(); // mark that the thread is finished latch.countDown(); @@ -197,19 +196,19 @@ public void accept(Long v) { @Test public void testMergeArrayWithThreading() { - final TestASynchronousFlowable o1 = new TestASynchronousFlowable(); - final TestASynchronousFlowable o2 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f1 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f2 = new TestASynchronousFlowable(); - Flowable<String> m = Flowable.merge(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)); - TestSubscriber<String> ts = new TestSubscriber<String>(stringObserver); + Flowable<String> m = Flowable.merge(Flowable.unsafeCreate(f1), Flowable.unsafeCreate(f2)); + TestSubscriber<String> ts = new TestSubscriber<String>(stringSubscriber); m.subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onComplete(); + verify(stringSubscriber, never()).onError(any(Throwable.class)); + verify(stringSubscriber, times(2)).onNext("hello"); + verify(stringSubscriber, times(1)).onComplete(); } @Test @@ -222,8 +221,8 @@ public void testSynchronizationOfMultipleSequencesLoop() throws Throwable { @Test public void testSynchronizationOfMultipleSequences() throws Throwable { - final TestASynchronousFlowable o1 = new TestASynchronousFlowable(); - final TestASynchronousFlowable o2 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f1 = new TestASynchronousFlowable(); + final TestASynchronousFlowable f2 = new TestASynchronousFlowable(); // use this latch to cause onNext to wait until we're ready to let it go final CountDownLatch endLatch = new CountDownLatch(1); @@ -233,7 +232,7 @@ public void testSynchronizationOfMultipleSequences() throws Throwable { final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); - Flowable<String> m = Flowable.merge(Flowable.unsafeCreate(o1), Flowable.unsafeCreate(o2)); + Flowable<String> m = Flowable.merge(Flowable.unsafeCreate(f1), Flowable.unsafeCreate(f2)); m.subscribe(new DefaultSubscriber<String>() { @Override @@ -267,8 +266,8 @@ public void onNext(String v) { }); // wait for both Flowables to send (one should be blocked) - o1.onNextBeingSent.await(); - o2.onNextBeingSent.await(); + f1.onNextBeingSent.await(); + f2.onNextBeingSent.await(); // I can't think of a way to know for sure that both threads have or are trying to send onNext // since I can't use a CountDownLatch for "after" onNext since I want to catch during it @@ -295,8 +294,8 @@ public void onNext(String v) { } try { - o1.t.join(); - o2.t.join(); + f1.t.join(); + f2.t.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -311,20 +310,20 @@ public void onNext(String v) { @Test public void testError1() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails - Flowable<String> m = Flowable.merge(o1, o2); - m.subscribe(stringObserver); + Flowable<String> m = Flowable.merge(f1, f2); + m.subscribe(stringSubscriber); - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(0)).onNext("one"); - verify(stringObserver, times(0)).onNext("two"); - verify(stringObserver, times(0)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(0)).onNext("one"); + verify(stringSubscriber, times(0)).onNext("two"); + verify(stringSubscriber, times(0)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(0)).onNext("five"); + verify(stringSubscriber, times(0)).onNext("six"); } /** @@ -333,32 +332,32 @@ public void testError1() { @Test public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Flowable<String> o1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); - final Flowable<String> o2 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" - final Flowable<String> o3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Flowable<String> o4 = Flowable.unsafeCreate(new TestErrorFlowable("nine"));// we expect to lose all of these since o2 is done first and fails - - Flowable<String> m = Flowable.merge(o1, o2, o3, o4); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onComplete(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - verify(stringObserver, times(0)).onNext("seven"); - verify(stringObserver, times(0)).onNext("eight"); - verify(stringObserver, times(0)).onNext("nine"); + final Flowable<String> f1 = Flowable.unsafeCreate(new TestErrorFlowable("one", "two", "three")); + final Flowable<String> f2 = Flowable.unsafeCreate(new TestErrorFlowable("four", null, "six")); // we expect to lose "six" + final Flowable<String> f3 = Flowable.unsafeCreate(new TestErrorFlowable("seven", "eight", null)); // we expect to lose all of these since o2 is done first and fails + final Flowable<String> f4 = Flowable.unsafeCreate(new TestErrorFlowable("nine")); // we expect to lose all of these since o2 is done first and fails + + Flowable<String> m = Flowable.merge(f1, f2, f3, f4); + m.subscribe(stringSubscriber); + + verify(stringSubscriber, times(1)).onError(any(NullPointerException.class)); + verify(stringSubscriber, never()).onComplete(); + verify(stringSubscriber, times(1)).onNext("one"); + verify(stringSubscriber, times(1)).onNext("two"); + verify(stringSubscriber, times(1)).onNext("three"); + verify(stringSubscriber, times(1)).onNext("four"); + verify(stringSubscriber, times(0)).onNext("five"); + verify(stringSubscriber, times(0)).onNext("six"); + verify(stringSubscriber, times(0)).onNext("seven"); + verify(stringSubscriber, times(0)).onNext("eight"); + verify(stringSubscriber, times(0)).onNext("nine"); } @Test @Ignore("Subscribe should not throw") public void testThrownErrorHandling() { TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable<String> o1 = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f1 = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> s) { @@ -367,7 +366,7 @@ public void subscribe(Subscriber<? super String> s) { }); - Flowable.merge(o1, o1).subscribe(ts); + Flowable.merge(f1, f1).subscribe(ts); ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); ts.assertTerminated(); System.out.println("Error: " + ts.errors()); @@ -376,10 +375,10 @@ public void subscribe(Subscriber<? super String> s) { private static class TestSynchronousFlowable implements Publisher<String> { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("hello"); - observer.onComplete(); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("hello"); + subscriber.onComplete(); } } @@ -388,20 +387,20 @@ private static class TestASynchronousFlowable implements Publisher<String> { final CountDownLatch onNextBeingSent = new CountDownLatch(1); @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override public void run() { onNextBeingSent.countDown(); try { - observer.onNext("hello"); + subscriber.onNext("hello"); // I can't use a countDownLatch to prove we are actually sending 'onNext' // since it will block if synchronized and I'll deadlock - observer.onComplete(); + subscriber.onComplete(); } catch (Exception e) { - observer.onError(e); + subscriber.onError(e); } } @@ -419,17 +418,17 @@ private static class TestErrorFlowable implements Publisher<String> { } @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); for (String s : valuesToReturn) { if (s == null) { System.out.println("throwing exception"); - observer.onError(new NullPointerException()); + subscriber.onError(new NullPointerException()); } else { - observer.onNext(s); + subscriber.onNext(s); } } - observer.onComplete(); + subscriber.onComplete(); } } @@ -437,14 +436,14 @@ public void subscribe(Subscriber<? super String> observer) { public void testUnsubscribeAsFlowablesComplete() { TestScheduler scheduler1 = new TestScheduler(); AtomicBoolean os1 = new AtomicBoolean(false); - Flowable<Long> o1 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1); + Flowable<Long> f1 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1); TestScheduler scheduler2 = new TestScheduler(); AtomicBoolean os2 = new AtomicBoolean(false); - Flowable<Long> o2 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); + Flowable<Long> f2 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); TestSubscriber<Long> ts = new TestSubscriber<Long>(); - Flowable.merge(o1, o2).subscribe(ts); + Flowable.merge(f1, f2).subscribe(ts); // we haven't incremented time so nothing should be received yet ts.assertNoValues(); @@ -479,14 +478,14 @@ public void testEarlyUnsubscribe() { for (int i = 0; i < 10; i++) { TestScheduler scheduler1 = new TestScheduler(); AtomicBoolean os1 = new AtomicBoolean(false); - Flowable<Long> o1 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1); + Flowable<Long> f1 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1); TestScheduler scheduler2 = new TestScheduler(); AtomicBoolean os2 = new AtomicBoolean(false); - Flowable<Long> o2 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); + Flowable<Long> f2 = createFlowableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); TestSubscriber<Long> ts = new TestSubscriber<Long>(); - Flowable.merge(o1, o2).subscribe(ts); + Flowable.merge(f1, f2).subscribe(ts); // we haven't incremented time so nothing should be received yet ts.assertNoValues(); @@ -557,10 +556,10 @@ public void onComplete() { @Test//(timeout = 10000) public void testConcurrency() { - Flowable<Integer> o = Flowable.range(1, 10000).subscribeOn(Schedulers.newThread()); + Flowable<Integer> f = Flowable.range(1, 10000).subscribeOn(Schedulers.newThread()); for (int i = 0; i < 10; i++) { - Flowable<Integer> merge = Flowable.merge(o.onBackpressureBuffer(), o.onBackpressureBuffer(), o.onBackpressureBuffer()); + Flowable<Integer> merge = Flowable.merge(f.onBackpressureBuffer(), f.onBackpressureBuffer(), f.onBackpressureBuffer()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); merge.subscribe(ts); @@ -577,7 +576,7 @@ public void testConcurrency() { @Test public void testConcurrencyWithSleeping() { - Flowable<Integer> o = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(final Subscriber<? super Integer> s) { @@ -613,7 +612,7 @@ public void run() { }); for (int i = 0; i < 10; i++) { - Flowable<Integer> merge = Flowable.merge(o, o, o); + Flowable<Integer> merge = Flowable.merge(f, f, f); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); merge.subscribe(ts); @@ -627,7 +626,7 @@ public void run() { @Test public void testConcurrencyWithBrokenOnCompleteContract() { - Flowable<Integer> o = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(final Subscriber<? super Integer> s) { @@ -660,7 +659,7 @@ public void run() { }); for (int i = 0; i < 10; i++) { - Flowable<Integer> merge = Flowable.merge(o.onBackpressureBuffer(), o.onBackpressureBuffer(), o.onBackpressureBuffer()); + Flowable<Integer> merge = Flowable.merge(f.onBackpressureBuffer(), f.onBackpressureBuffer(), f.onBackpressureBuffer()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); merge.subscribe(ts); @@ -676,9 +675,9 @@ public void run() { @Test public void testBackpressureUpstream() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); + Flowable<Integer> f1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); final AtomicInteger generated2 = new AtomicInteger(); - Flowable<Integer> o2 = createInfiniteFlowable(generated2).subscribeOn(Schedulers.computation()); + Flowable<Integer> f2 = createInfiniteFlowable(generated2).subscribeOn(Schedulers.computation()); TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>() { @Override @@ -688,7 +687,7 @@ public void onNext(Integer t) { } }; - Flowable.merge(o1.take(Flowable.bufferSize() * 2), o2.take(Flowable.bufferSize() * 2)).subscribe(testSubscriber); + Flowable.merge(f1.take(Flowable.bufferSize() * 2), f2.take(Flowable.bufferSize() * 2)).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); if (testSubscriber.errors().size() > 0) { testSubscriber.errors().get(0).printStackTrace(); @@ -715,7 +714,7 @@ public void testBackpressureUpstream2InLoop() throws InterruptedException { @Test public void testBackpressureUpstream2() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); + Flowable<Integer> f1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>() { @Override @@ -724,7 +723,7 @@ public void onNext(Integer t) { } }; - Flowable.merge(o1.take(Flowable.bufferSize() * 2), Flowable.just(-99)).subscribe(testSubscriber); + Flowable.merge(f1.take(Flowable.bufferSize() * 2), Flowable.just(-99)).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); List<Integer> onNextEvents = testSubscriber.values(); @@ -750,9 +749,9 @@ public void onNext(Integer t) { @Test(timeout = 10000) public void testBackpressureDownstreamWithConcurrentStreams() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); + Flowable<Integer> f1 = createInfiniteFlowable(generated1).subscribeOn(Schedulers.computation()); final AtomicInteger generated2 = new AtomicInteger(); - Flowable<Integer> o2 = createInfiniteFlowable(generated2).subscribeOn(Schedulers.computation()); + Flowable<Integer> f2 = createInfiniteFlowable(generated2).subscribeOn(Schedulers.computation()); TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>() { @Override @@ -770,7 +769,7 @@ public void onNext(Integer t) { } }; - Flowable.merge(o1.take(Flowable.bufferSize() * 2), o2.take(Flowable.bufferSize() * 2)).observeOn(Schedulers.computation()).subscribe(testSubscriber); + Flowable.merge(f1.take(Flowable.bufferSize() * 2), f2.take(Flowable.bufferSize() * 2)).observeOn(Schedulers.computation()).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); if (testSubscriber.errors().size() > 0) { testSubscriber.errors().get(0).printStackTrace(); @@ -787,7 +786,7 @@ public void onNext(Integer t) { @Test public void testBackpressureBothUpstreamAndDownstreamWithSynchronousScalarFlowables() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); - Flowable<Flowable<Integer>> o1 = createInfiniteFlowable(generated1) + Flowable<Flowable<Integer>> f1 = createInfiniteFlowable(generated1) .map(new Function<Integer, Flowable<Integer>>() { @Override @@ -813,7 +812,7 @@ public void onNext(Integer t) { } }; - Flowable.merge(o1).observeOn(Schedulers.computation()).take(Flowable.bufferSize() * 2).subscribe(testSubscriber); + Flowable.merge(f1).observeOn(Schedulers.computation()).take(Flowable.bufferSize() * 2).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); if (testSubscriber.errors().size() > 0) { testSubscriber.errors().get(0).printStackTrace(); @@ -841,7 +840,7 @@ public void onNext(Integer t) { @Test(timeout = 5000) public void testBackpressureBothUpstreamAndDownstreamWithRegularFlowables() throws InterruptedException { final AtomicInteger generated1 = new AtomicInteger(); - Flowable<Flowable<Integer>> o1 = createInfiniteFlowable(generated1).map(new Function<Integer, Flowable<Integer>>() { + Flowable<Flowable<Integer>> f1 = createInfiniteFlowable(generated1).map(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t1) { @@ -868,7 +867,7 @@ public void onNext(Integer t) { } }; - Flowable.merge(o1).observeOn(Schedulers.computation()).take(Flowable.bufferSize() * 2).subscribe(testSubscriber); + Flowable.merge(f1).observeOn(Schedulers.computation()).take(Flowable.bufferSize() * 2).subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); if (testSubscriber.errors().size() > 0) { testSubscriber.errors().get(0).printStackTrace(); @@ -1269,11 +1268,11 @@ public void run() { @Test public void testMergeRequestOverflow() throws InterruptedException { //do a non-trivial merge so that future optimisations with EMPTY don't invalidate this test - Flowable<Integer> o = Flowable.fromIterable(Arrays.asList(1,2)) - .mergeWith(Flowable.fromIterable(Arrays.asList(3,4))); + Flowable<Integer> f = Flowable.fromIterable(Arrays.asList(1, 2)) + .mergeWith(Flowable.fromIterable(Arrays.asList(3, 4))); final int expectedCount = 4; final CountDownLatch latch = new CountDownLatch(expectedCount); - o.subscribeOn(Schedulers.computation()).subscribe(new DefaultSubscriber<Integer>() { + f.subscribeOn(Schedulers.computation()).subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { @@ -1360,10 +1359,12 @@ void runMerge(Function<Integer, Flowable<Integer>> func, TestSubscriber<Integer> public void testFastMergeFullScalar() { runMerge(toScalar, new TestSubscriber<Integer>()); } + @Test public void testFastMergeHiddenScalar() { runMerge(toHiddenScalar, new TestSubscriber<Integer>()); } + @Test public void testSlowMergeFullScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { @@ -1382,6 +1383,7 @@ public void onNext(Integer t) { runMerge(toScalar, ts); } } + @Test public void testSlowMergeHiddenScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { @@ -1462,7 +1464,6 @@ public void mergeConcurrentJustRange() { ts.assertComplete(); } - @SuppressWarnings("unchecked") @Test @Ignore("No 2-9 argument merge()") @@ -1494,22 +1495,22 @@ public void mergeMany() throws Exception { public void mergeArrayMaxConcurrent() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps1 = PublishProcessor.create(); - PublishProcessor<Integer> ps2 = PublishProcessor.create(); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); - Flowable.mergeArray(1, 128, new Flowable[] { ps1, ps2 }).subscribe(ts); + Flowable.mergeArray(1, 128, new Flowable[] { pp1, pp2 }).subscribe(ts); - assertTrue("ps1 has no subscribers?!", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?!", ps2.hasSubscribers()); + assertTrue("ps1 has no subscribers?!", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?!", pp2.hasSubscribers()); - ps1.onNext(1); - ps1.onComplete(); + pp1.onNext(1); + pp1.onComplete(); - assertFalse("ps1 has subscribers?!", ps1.hasSubscribers()); - assertTrue("ps2 has no subscribers?!", ps2.hasSubscribers()); + assertFalse("ps1 has subscribers?!", pp1.hasSubscribers()); + assertTrue("ps2 has no subscribers?!", pp2.hasSubscribers()); - ps2.onNext(2); - ps2.onComplete(); + pp2.onNext(2); + pp2.onComplete(); ts.assertValues(1, 2); ts.assertNoErrors(); @@ -1571,15 +1572,15 @@ public void noInnerReordering() { new FlowableFlatMap.MergeSubscriber<Publisher<Integer>, Integer>(ts, Functions.<Publisher<Integer>>identity(), false, 128, 128); ms.onSubscribe(new BooleanSubscription()); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ms.onNext(ps); + ms.onNext(pp); - ps.onNext(1); + pp.onNext(1); BackpressureHelper.add(ms.requested, 2); - ps.onNext(2); + pp.onNext(2); ms.drain(); @@ -1622,7 +1623,6 @@ public void array() { } } - @SuppressWarnings("unchecked") @Test public void mergeArray() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java new file mode 100644 index 0000000000..18f5551e4a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import org.junit.Test; + +import static org.junit.Assert.*; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.CompletableSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableMergeWithCompletableTest { + + @Test + public void normal() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.range(1, 5).mergeWith( + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + }) + ) + .subscribe(ts); + + ts.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void take() { + Flowable.range(1, 5) + .mergeWith(Completable.complete()) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void normalBackpressured() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); + + Flowable.range(1, 5).mergeWith( + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + ts.onNext(100); + } + }) + ) + .subscribe(ts); + + ts + .assertValue(100) + .requestMore(2) + .assertValues(100, 1, 2) + .requestMore(2) + .assertValues(100, 1, 2, 3, 4) + .requestMore(1) + .assertResult(100, 1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .mergeWith(Completable.complete()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Flowable.never() + .mergeWith(Completable.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 1000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(1); + } + } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", cs.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", cs.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java new file mode 100644 index 0000000000..676c9074c3 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java @@ -0,0 +1,440 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.MaybeSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableMergeWithMaybeTest { + + @Test + public void normal() { + Flowable.range(1, 5) + .mergeWith(Maybe.just(100)) + .test() + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void emptyOther() { + Flowable.range(1, 5) + .mergeWith(Maybe.<Integer>empty()) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalLong() { + Flowable.range(1, 512) + .mergeWith(Maybe.just(100)) + .test() + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void normalLongRequestExact() { + Flowable.range(1, 512) + .mergeWith(Maybe.just(100)) + .test(513) + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void take() { + Flowable.range(1, 5) + .mergeWith(Maybe.just(100)) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void normalBackpressured() { + Flowable.range(1, 5).mergeWith( + Maybe.just(100) + ) + .test(0L) + .assertEmpty() + .requestMore(2) + .assertValues(100, 1) + .requestMore(2) + .assertValues(100, 1, 2, 3) + .requestMore(2) + .assertResult(100, 1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .mergeWith(Maybe.just(100)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Flowable.never() + .mergeWith(Maybe.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(1, 1); + } + } + + @Test + public void onNextSlowPath() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + pp.onNext(2); + } + } + }); + + pp.onNext(1); + cs.onSuccess(3); + + pp.onNext(4); + pp.onComplete(); + + ts.assertResult(1, 2, 3, 4); + } + + @Test + public void onSuccessSlowPath() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onNext(3); + pp.onComplete(); + + ts.assertResult(1, 2, 3); + } + + @Test + public void onSuccessSlowPathBackpressured() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onNext(3); + pp.onComplete(); + + ts.request(2); + ts.assertResult(1, 2, 3); + } + + @Test + public void onSuccessFastPathBackpressuredRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>(0)); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(2); + } + }; + + TestHelper.race(r1, r2); + + pp.onNext(2); + pp.onComplete(); + + ts.assertResult(1, 2); + } + } + + @Test + public void onErrorMainOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<?>> subscriber = new AtomicReference<Subscriber<?>>(); + TestSubscriber<Integer> ts = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + subscriber.set(s); + } + } + .mergeWith(Maybe.<Integer>error(new IOException())) + .test(); + + subscriber.get().onError(new TestException()); + + ts.assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onErrorOtherOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.error(new IOException()) + .mergeWith(Maybe.error(new TestException())) + .test() + .assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onNextRequestRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.mergeWith(cs).test(0); + + pp.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(3); + } + }; + + TestHelper.race(r1, r2); + + cs.onSuccess(1); + pp.onComplete(); + + ts.assertResult(0, 1, 1); + } + } + + @Test + public void doubleOnSubscribeMain() { + TestHelper.checkDoubleOnSubscribeFlowable( + new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f.mergeWith(Maybe.just(1)); + } + } + ); + } + + @Test + public void noRequestOnError() { + Flowable.empty() + .mergeWith(Maybe.error(new TestException())) + .test(0) + .assertFailure(TestException.class); + } + + @Test + public void drainExactRequestCancel() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs) + .limit(2) + .subscribeWith(new TestSubscriber<Integer>(2) { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onComplete(); + + ts.request(2); + ts.assertResult(1, 2); + } + + @Test + public void drainRequestWhenLimitReached() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs) + .subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 0; i < Flowable.bufferSize() - 1; i++) { + pp.onNext(i + 2); + } + } + } + }); + + cs.onSuccess(1); + + pp.onComplete(); + + ts.request(2); + ts.assertValueCount(Flowable.bufferSize()); + ts.assertComplete(); + } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(ms).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ms.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(ms).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + ms.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ms.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java new file mode 100644 index 0000000000..6a182785da --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java @@ -0,0 +1,436 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.SingleSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableMergeWithSingleTest { + + @Test + public void normal() { + Flowable.range(1, 5) + .mergeWith(Single.just(100)) + .test() + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void normalLong() { + Flowable.range(1, 512) + .mergeWith(Single.just(100)) + .test() + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void normalLongRequestExact() { + Flowable.range(1, 512) + .mergeWith(Single.just(100)) + .test(513) + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void take() { + Flowable.range(1, 5) + .mergeWith(Single.just(100)) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void normalBackpressured() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); + + Flowable.range(1, 5).mergeWith( + Single.just(100) + ) + .subscribe(ts); + + ts + .assertEmpty() + .requestMore(2) + .assertValues(100, 1) + .requestMore(2) + .assertValues(100, 1, 2, 3) + .requestMore(2) + .assertResult(100, 1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .mergeWith(Single.just(100)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Flowable.never() + .mergeWith(Single.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(1, 1); + } + } + + @Test + public void onNextSlowPath() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + pp.onNext(2); + } + } + }); + + pp.onNext(1); + cs.onSuccess(3); + + pp.onNext(4); + pp.onComplete(); + + ts.assertResult(1, 2, 3, 4); + } + + @Test + public void onSuccessSlowPath() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onNext(3); + pp.onComplete(); + + ts.assertResult(1, 2, 3); + } + + @Test + public void onSuccessSlowPathBackpressured() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onNext(3); + pp.onComplete(); + + ts.request(2); + ts.assertResult(1, 2, 3); + } + + @Test + public void onSuccessFastPathBackpressuredRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.mergeWith(cs).subscribeWith(new TestSubscriber<Integer>(0)); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(2); + } + }; + + TestHelper.race(r1, r2); + + pp.onNext(2); + pp.onComplete(); + + ts.assertResult(1, 2); + } + } + + @Test + public void onErrorMainOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<?>> subscriber = new AtomicReference<Subscriber<?>>(); + TestSubscriber<Integer> ts = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + subscriber.set(s); + } + } + .mergeWith(Single.<Integer>error(new IOException())) + .test(); + + subscriber.get().onError(new TestException()); + + ts.assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onErrorOtherOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.error(new IOException()) + .mergeWith(Single.error(new TestException())) + .test() + .assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onNextRequestRace() { + for (int i = 0; i < 10000; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.mergeWith(cs).test(0); + + pp.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(3); + } + }; + + TestHelper.race(r1, r2); + + cs.onSuccess(1); + pp.onComplete(); + + ts.assertResult(0, 1, 1); + } + } + + @Test + public void doubleOnSubscribeMain() { + TestHelper.checkDoubleOnSubscribeFlowable( + new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f.mergeWith(Single.just(1)); + } + } + ); + } + + @Test + public void noRequestOnError() { + Flowable.empty() + .mergeWith(Single.error(new TestException())) + .test(0) + .assertFailure(TestException.class); + } + + @Test + public void drainExactRequestCancel() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs) + .limit(2) + .subscribeWith(new TestSubscriber<Integer>(2) { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + pp.onNext(1); + + pp.onComplete(); + + ts.request(2); + ts.assertResult(1, 2); + } + + @Test + public void drainRequestWhenLimitReached() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(cs) + .subscribeWith(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 0; i < Flowable.bufferSize() - 1; i++) { + pp.onNext(i + 2); + } + } + } + }); + + cs.onSuccess(1); + + pp.onComplete(); + + ts.request(2); + ts.assertValueCount(Flowable.bufferSize()); + ts.assertComplete(); + } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + SingleSubject<Integer> ss = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(ss).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ss.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ss.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + SingleSubject<Integer> ss = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.mergeWith(ss).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ss.hasObservers()); + + ss.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ss.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMulticastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMulticastTest.java deleted file mode 100644 index 0a838499a3..0000000000 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMulticastTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.operators.flowable; - -public class FlowableMulticastTest { -// @Test -// public void testMulticast() { -// Processor<String, String> source = PublishProcessor.create(); -// -// ConnectableObservable<String> multicasted = new OperatorMulticast<String, String>(source, new PublishProcessorFactory()); -// -// @SuppressWarnings("unchecked") -// Observer<String> observer = mock(Observer.class); -// multicasted.subscribe(observer); -// -// source.onNext("one"); -// source.onNext("two"); -// -// multicasted.connect(); -// -// source.onNext("three"); -// source.onNext("four"); -// source.onComplete(); -// -// verify(observer, never()).onNext("one"); -// verify(observer, never()).onNext("two"); -// verify(observer, times(1)).onNext("three"); -// verify(observer, times(1)).onNext("four"); -// verify(observer, times(1)).onComplete(); -// -// } -// -// @Test -// public void testMulticastConnectTwice() { -// Processor<String, String> source = PublishProcessor.create(); -// -// ConnectableObservable<String> multicasted = new OperatorMulticast<String, String>(source, new PublishProcessorFactory()); -// -// @SuppressWarnings("unchecked") -// Observer<String> observer = mock(Observer.class); -// multicasted.subscribe(observer); -// -// source.onNext("one"); -// -// Subscription sub = multicasted.connect(); -// Subscription sub2 = multicasted.connect(); -// -// source.onNext("two"); -// source.onComplete(); -// -// verify(observer, never()).onNext("one"); -// verify(observer, times(1)).onNext("two"); -// verify(observer, times(1)).onComplete(); -// -// assertEquals(sub, sub2); -// -// } -// -// @Test -// public void testMulticastDisconnect() { -// Processor<String, String> source = PublishProcessor.create(); -// -// ConnectableObservable<String> multicasted = new OperatorMulticast<String, String>(source, new PublishProcessorFactory()); -// -// @SuppressWarnings("unchecked") -// Observer<String> observer = mock(Observer.class); -// multicasted.subscribe(observer); -// -// source.onNext("one"); -// -// Subscription connection = multicasted.connect(); -// source.onNext("two"); -// -// connection.unsubscribe(); -// source.onNext("three"); -// -// // subscribe again -// multicasted.subscribe(observer); -// // reconnect -// multicasted.connect(); -// source.onNext("four"); -// source.onComplete(); -// -// verify(observer, never()).onNext("one"); -// verify(observer, times(1)).onNext("two"); -// verify(observer, never()).onNext("three"); -// verify(observer, times(1)).onNext("four"); -// verify(observer, times(1)).onComplete(); -// -// } -// -// private static final class PublishProcessorFactory implements Callable<Processor<String, String>> { -// -// @Override -// public Subject<String, String> call() { -// return PublishProcessor.<String> create(); -// } -// -// } -} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java index 77bdf9e8b3..a5abff03ef 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java @@ -21,12 +21,13 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import io.reactivex.annotations.Nullable; import org.junit.Test; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; @@ -34,6 +35,7 @@ import io.reactivex.internal.operators.flowable.FlowableObserveOn.BaseObserveOnSubscriber; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.*; @@ -46,13 +48,13 @@ public class FlowableObserveOnTest { */ @Test public void testObserveOn() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - Flowable.just(1, 2, 3).observeOn(ImmediateThinScheduler.INSTANCE).subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + Flowable.just(1, 2, 3).observeOn(ImmediateThinScheduler.INSTANCE).subscribe(subscriber); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, times(1)).onComplete(); } @Test @@ -61,10 +63,10 @@ public void testOrdering() throws InterruptedException { // FIXME null values not allowed Flowable<String> obs = Flowable.just("one", "null", "two", "three", "four"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + InOrder inOrder = inOrder(subscriber); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); obs.observeOn(Schedulers.computation()).subscribe(ts); @@ -76,12 +78,12 @@ public void testOrdering() throws InterruptedException { fail("failed with exception"); } - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("null"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("null"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -92,7 +94,7 @@ public void testThreadName() throws InterruptedException { // Flowable<String> obs = Flowable.just("one", null, "two", "three", "four"); Flowable<String> obs = Flowable.just("one", "null", "two", "three", "four"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final String parentThreadName = Thread.currentThread().getName(); final CountDownLatch completedLatch = new CountDownLatch(1); @@ -127,45 +129,45 @@ public void run() { completedLatch.countDown(); } - }).subscribe(observer); + }).subscribe(subscriber); if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) { fail("timed out waiting"); } - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(5)).onNext(any(String.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(5)).onNext(any(String.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void observeOnTheSameSchedulerTwice() { Scheduler scheduler = ImmediateThinScheduler.INSTANCE; - Flowable<Integer> o = Flowable.just(1, 2, 3); - Flowable<Integer> o2 = o.observeOn(scheduler); + Flowable<Integer> f = Flowable.just(1, 2, 3); + Flowable<Integer> f2 = f.observeOn(scheduler); - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - Subscriber<Object> observer2 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); - InOrder inOrder1 = inOrder(observer1); - InOrder inOrder2 = inOrder(observer2); + InOrder inOrder1 = inOrder(subscriber1); + InOrder inOrder2 = inOrder(subscriber2); - o2.subscribe(observer1); - o2.subscribe(observer2); + f2.subscribe(subscriber1); + f2.subscribe(subscriber2); - inOrder1.verify(observer1, times(1)).onNext(1); - inOrder1.verify(observer1, times(1)).onNext(2); - inOrder1.verify(observer1, times(1)).onNext(3); - inOrder1.verify(observer1, times(1)).onComplete(); - verify(observer1, never()).onError(any(Throwable.class)); + inOrder1.verify(subscriber1, times(1)).onNext(1); + inOrder1.verify(subscriber1, times(1)).onNext(2); + inOrder1.verify(subscriber1, times(1)).onNext(3); + inOrder1.verify(subscriber1, times(1)).onComplete(); + verify(subscriber1, never()).onError(any(Throwable.class)); inOrder1.verifyNoMoreInteractions(); - inOrder2.verify(observer2, times(1)).onNext(1); - inOrder2.verify(observer2, times(1)).onNext(2); - inOrder2.verify(observer2, times(1)).onNext(3); - inOrder2.verify(observer2, times(1)).onComplete(); - verify(observer2, never()).onError(any(Throwable.class)); + inOrder2.verify(subscriber2, times(1)).onNext(1); + inOrder2.verify(subscriber2, times(1)).onNext(2); + inOrder2.verify(subscriber2, times(1)).onNext(3); + inOrder2.verify(subscriber2, times(1)).onComplete(); + verify(subscriber2, never()).onError(any(Throwable.class)); inOrder2.verifyNoMoreInteractions(); } @@ -174,34 +176,34 @@ public void observeSameOnMultipleSchedulers() { TestScheduler scheduler1 = new TestScheduler(); TestScheduler scheduler2 = new TestScheduler(); - Flowable<Integer> o = Flowable.just(1, 2, 3); - Flowable<Integer> o1 = o.observeOn(scheduler1); - Flowable<Integer> o2 = o.observeOn(scheduler2); + Flowable<Integer> f = Flowable.just(1, 2, 3); + Flowable<Integer> f1 = f.observeOn(scheduler1); + Flowable<Integer> f2 = f.observeOn(scheduler2); - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - Subscriber<Object> observer2 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); - InOrder inOrder1 = inOrder(observer1); - InOrder inOrder2 = inOrder(observer2); + InOrder inOrder1 = inOrder(subscriber1); + InOrder inOrder2 = inOrder(subscriber2); - o1.subscribe(observer1); - o2.subscribe(observer2); + f1.subscribe(subscriber1); + f2.subscribe(subscriber2); scheduler1.advanceTimeBy(1, TimeUnit.SECONDS); scheduler2.advanceTimeBy(1, TimeUnit.SECONDS); - inOrder1.verify(observer1, times(1)).onNext(1); - inOrder1.verify(observer1, times(1)).onNext(2); - inOrder1.verify(observer1, times(1)).onNext(3); - inOrder1.verify(observer1, times(1)).onComplete(); - verify(observer1, never()).onError(any(Throwable.class)); + inOrder1.verify(subscriber1, times(1)).onNext(1); + inOrder1.verify(subscriber1, times(1)).onNext(2); + inOrder1.verify(subscriber1, times(1)).onNext(3); + inOrder1.verify(subscriber1, times(1)).onComplete(); + verify(subscriber1, never()).onError(any(Throwable.class)); inOrder1.verifyNoMoreInteractions(); - inOrder2.verify(observer2, times(1)).onNext(1); - inOrder2.verify(observer2, times(1)).onNext(2); - inOrder2.verify(observer2, times(1)).onNext(3); - inOrder2.verify(observer2, times(1)).onComplete(); - verify(observer2, never()).onError(any(Throwable.class)); + inOrder2.verify(subscriber2, times(1)).onNext(1); + inOrder2.verify(subscriber2, times(1)).onNext(2); + inOrder2.verify(subscriber2, times(1)).onNext(3); + inOrder2.verify(subscriber2, times(1)).onComplete(); + verify(subscriber2, never()).onError(any(Throwable.class)); inOrder2.verifyNoMoreInteractions(); } @@ -366,27 +368,26 @@ public void testDelayedErrorDeliveryWhenSafeSubscriberUnsubscribes() { Flowable<Integer> source = Flowable.concat(Flowable.<Integer> error(new TestException()), Flowable.just(1)); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.observeOn(testScheduler).subscribe(o); + source.observeOn(testScheduler).subscribe(subscriber); - inOrder.verify(o, never()).onError(any(TestException.class)); + inOrder.verify(subscriber, never()).onError(any(TestException.class)); testScheduler.advanceTimeBy(1, TimeUnit.SECONDS); - inOrder.verify(o).onError(any(TestException.class)); - inOrder.verify(o, never()).onNext(anyInt()); - inOrder.verify(o, never()).onComplete(); + inOrder.verify(subscriber).onError(any(TestException.class)); + inOrder.verify(subscriber, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onComplete(); } @Test public void testAfterUnsubscribeCalledThenObserverOnNextNeverCalled() { final TestScheduler testScheduler = new TestScheduler(); - final Subscriber<Integer> observer = TestHelper.mockSubscriber(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(observer); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(subscriber); Flowable.just(1, 2, 3) .observeOn(testScheduler) @@ -395,11 +396,11 @@ public void testAfterUnsubscribeCalledThenObserverOnNextNeverCalled() { ts.dispose(); testScheduler.advanceTimeBy(1, TimeUnit.SECONDS); - final InOrder inOrder = inOrder(observer); + final InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, never()).onNext(anyInt()); - inOrder.verify(observer, never()).onError(any(Exception.class)); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onError(any(Exception.class)); + inOrder.verify(subscriber, never()).onComplete(); } @Test @@ -538,13 +539,13 @@ public void testQueueFullEmitsError() { Flowable<Integer> flowable = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(Subscriber<? super Integer> o) { - o.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); for (int i = 0; i < Flowable.bufferSize() + 10; i++) { - o.onNext(i); + subscriber.onNext(i); } latch.countDown(); - o.onComplete(); + subscriber.onComplete(); } }); @@ -601,7 +602,7 @@ public void testAsyncChild() { @Test public void testOnErrorCutsAheadOfOnNext() { for (int i = 0; i < 50; i++) { - final PublishProcessor<Long> subject = PublishProcessor.create(); + final PublishProcessor<Long> processor = PublishProcessor.create(); final AtomicLong counter = new AtomicLong(); TestSubscriber<Long> ts = new TestSubscriber<Long>(new DefaultSubscriber<Long>() { @@ -626,11 +627,11 @@ public void onNext(Long t) { } }); - subject.observeOn(Schedulers.computation()).subscribe(ts); + processor.observeOn(Schedulers.computation()).subscribe(ts); // this will blow up with backpressure while (counter.get() < 102400) { - subject.onNext(counter.get()); + processor.onNext(counter.get()); counter.incrementAndGet(); } @@ -795,12 +796,11 @@ public void onNext(Integer t) { assertEquals(Arrays.asList(128L), requests); } - @Test public void testErrorDelayed() { TestScheduler s = new TestScheduler(); - Flowable<Integer> source = Flowable.just(1, 2 ,3) + Flowable<Integer> source = Flowable.just(1, 2, 3) .concatWith(Flowable.<Integer>error(new TestException())); TestSubscriber<Integer> ts = TestSubscriber.create(0); @@ -834,7 +834,7 @@ public void testErrorDelayed() { @Test public void testErrorDelayedAsync() { - Flowable<Integer> source = Flowable.just(1, 2 ,3) + Flowable<Integer> source = Flowable.just(1, 2, 3) .concatWith(Flowable.<Integer>error(new TestException())); TestSubscriber<Integer> ts = TestSubscriber.create(); @@ -1006,7 +1006,7 @@ public void cancelCleanup() { @Test public void conditionalConsumerFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .observeOn(Schedulers.single()) @@ -1020,14 +1020,14 @@ public boolean test(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .awaitDone(5, TimeUnit.SECONDS) .assertResult(2, 4); } @Test public void conditionalConsumerFusedReject() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5) .observeOn(Schedulers.single()) @@ -1041,7 +1041,7 @@ public boolean test(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .awaitDone(5, TimeUnit.SECONDS) .assertResult(2, 4); } @@ -1071,7 +1071,7 @@ public void requestOneConditional() throws Exception { @Test public void conditionalConsumerFusedAsync() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -1094,14 +1094,14 @@ public boolean test(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .awaitDone(5, TimeUnit.SECONDS) .assertResult(2, 4); } @Test public void conditionalConsumerHidden() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5).hide() .observeOn(Schedulers.single()) @@ -1115,14 +1115,14 @@ public boolean test(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .awaitDone(5, TimeUnit.SECONDS) .assertResult(2, 4); } @Test public void conditionalConsumerBarrier() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5) .map(Functions.<Integer>identity()) @@ -1137,7 +1137,7 @@ public boolean test(Integer v) throws Exception { ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .awaitDone(5, TimeUnit.SECONDS) .assertResult(2, 4); } @@ -1151,8 +1151,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.observeOn(new TestScheduler()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.observeOn(new TestScheduler()); } }); } @@ -1162,14 +1162,14 @@ public void badSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { TestScheduler scheduler = new TestScheduler(); - TestSubscriber<Integer> to = new Flowable<Integer>() { + TestSubscriber<Integer> ts = new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onNext(1); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .observeOn(scheduler) @@ -1177,7 +1177,7 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { scheduler.triggerActions(); - to.assertResult(); + ts.assertResult(); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { @@ -1198,11 +1198,11 @@ public void inputSyncFused() { public void inputAsyncFused() { UnicastProcessor<Integer> us = UnicastProcessor.create(); - TestSubscriber<Integer> to = us.observeOn(Schedulers.single()).test(); + TestSubscriber<Integer> ts = us.observeOn(Schedulers.single()).test(); TestHelper.emit(us, 1, 2, 3, 4, 5); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5); } @@ -1211,11 +1211,11 @@ public void inputAsyncFused() { public void inputAsyncFusedError() { UnicastProcessor<Integer> us = UnicastProcessor.create(); - TestSubscriber<Integer> to = us.observeOn(Schedulers.single()).test(); + TestSubscriber<Integer> ts = us.observeOn(Schedulers.single()).test(); us.onError(new TestException()); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @@ -1224,77 +1224,77 @@ public void inputAsyncFusedError() { public void inputAsyncFusedErrorDelayed() { UnicastProcessor<Integer> us = UnicastProcessor.create(); - TestSubscriber<Integer> to = us.observeOn(Schedulers.single(), true).test(); + TestSubscriber<Integer> ts = us.observeOn(Schedulers.single(), true).test(); us.onError(new TestException()); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @Test public void outputFused() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 5).hide() .observeOn(Schedulers.single()) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5); } @Test public void outputFusedReject() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.range(1, 5).hide() .observeOn(Schedulers.single()) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5); } @Test public void inputOutputAsyncFusedError() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); us.observeOn(Schedulers.single()) - .subscribe(to); + .subscribe(ts); us.onError(new TestException()); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); - SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @Test public void inputOutputAsyncFusedErrorDelayed() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); us.observeOn(Schedulers.single(), true) - .subscribe(to); + .subscribe(ts); us.onError(new TestException()); - to + ts .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); - SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @@ -1307,19 +1307,19 @@ public void outputFusedCancelReentrant() throws Exception { us.observeOn(Schedulers.single()) .subscribe(new FlowableSubscriber<Integer>() { - Subscription d; + Subscription upstream; int count; @Override - public void onSubscribe(Subscription d) { - this.d = d; - ((QueueSubscription<?>)d).requestFusion(QueueSubscription.ANY); + public void onSubscribe(Subscription s) { + this.upstream = s; + ((QueueSubscription<?>)s).requestFusion(QueueFuseable.ANY); } @Override public void onNext(Integer value) { if (++count == 1) { us.onNext(2); - d.cancel(); + upstream.cancel(); cdl.countDown(); } } @@ -1344,11 +1344,11 @@ public void onComplete() { public void nonFusedPollThrows() { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); @SuppressWarnings("unchecked") - BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer; + BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)subscriber; oo.sourceMode = QueueFuseable.SYNC; oo.requested.lazySet(1); @@ -1395,11 +1395,11 @@ public void clear() { public void conditionalNonFusedPollThrows() { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); @SuppressWarnings("unchecked") - BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer; + BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)subscriber; oo.sourceMode = QueueFuseable.SYNC; oo.requested.lazySet(1); @@ -1447,11 +1447,11 @@ public void clear() { public void asycFusedPollThrows() { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); @SuppressWarnings("unchecked") - BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer; + BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)subscriber; oo.sourceMode = QueueFuseable.ASYNC; oo.requested.lazySet(1); @@ -1498,11 +1498,11 @@ public void clear() { public void conditionalAsyncFusedPollThrows() { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); @SuppressWarnings("unchecked") - BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer; + BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)subscriber; oo.sourceMode = QueueFuseable.ASYNC; oo.requested.lazySet(1); @@ -1712,14 +1712,14 @@ public void request1Conditional() { @Test public void backFusedConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 100).hide() .observeOn(ImmediateThinScheduler.INSTANCE) .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertValueCount(100) .assertComplete() .assertNoErrors(); @@ -1727,21 +1727,21 @@ public void backFusedConditional() { @Test public void backFusedErrorConditional() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.<Integer>error(new TestException()) .observeOn(ImmediateThinScheduler.INSTANCE) .filter(Functions.alwaysTrue()) .subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC) .assertFailure(TestException.class); } @Test public void backFusedCancelConditional() { - for (int i = 0; i < 500; i++) { - final TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); final TestScheduler scheduler = new TestScheduler(); @@ -1766,7 +1766,7 @@ public void run() { TestHelper.race(r1, r2); - SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC); + SubscriberFusion.assertFusion(ts, QueueFuseable.ASYNC); if (ts.valueCount() != 0) { ts.assertResult(1); @@ -1783,4 +1783,196 @@ public void syncFusedRequestOneByOneConditional() { .test() .assertResult(1, 2, 3, 4, 5); } + + public static final class DisposeTrackingScheduler extends Scheduler { + + public final AtomicInteger disposedCount = new AtomicInteger(); + + @Override + public Worker createWorker() { + return new TrackingWorker(); + } + + final class TrackingWorker extends Scheduler.Worker { + + @Override + public void dispose() { + disposedCount.getAndIncrement(); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public Disposable schedule(Runnable run, long delay, + TimeUnit unit) { + run.run(); + return Disposables.empty(); + } + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).hide().observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastProcessor<Integer> up = UnicastProcessor.create(); + up.onNext(1); + up.onComplete(); + + Flowable.concat( + up.observeOn(s), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + static final class TestSubscriberFusedCanceling + extends TestSubscriber<Integer> { + + TestSubscriberFusedCanceling() { + super(); + initialFusionMode = QueueFuseable.ANY; + } + + @Override + public void onComplete() { + cancel(); + super.onComplete(); + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestSubscriber<Integer> ts = new TestSubscriberFusedCanceling(); + + Flowable.just(1).hide().observeOn(s).subscribe(ts); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).hide().observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Flowable.concat( + Flowable.just(1).observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastProcessor<Integer> up = UnicastProcessor.create(); + up.onNext(1); + up.onComplete(); + + Flowable.concat( + up.observeOn(s).filter(Functions.alwaysTrue()), + Flowable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOutConditional() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestSubscriber<Integer> ts = new TestSubscriberFusedCanceling(); + + Flowable.just(1).hide().observeOn(s).filter(Functions.alwaysTrue()).subscribe(ts); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final UnicastProcessor<Integer> up = UnicastProcessor.create(); + + TestObserver<Integer> to = up.hide() + .observeOn(Schedulers.io()) + .observeOn(Schedulers.single()) + .unsubscribeOn(Schedulers.computation()) + .firstOrError() + .test(); + + for (int i = 0; up.hasSubscribers() && i < 10000; i++) { + up.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java index 85468f8753..c52888548d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategyTest.java @@ -116,7 +116,6 @@ public void subscribe(Subscriber<? super Long> s) { } }); - @Test(expected = IllegalArgumentException.class) public void backpressureBufferNegativeCapacity() throws InterruptedException { Flowable.empty().onBackpressureBuffer(-1, EMPTY_ACTION , DROP_OLDEST); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java index 672b94cb1b..be7fe522da 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java @@ -15,17 +15,22 @@ import static org.junit.Assert.*; +import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; @@ -143,7 +148,7 @@ public void run() { int size = ts.values().size(); assertTrue(size <= 150); // will get up to 50 more - assertTrue(ts.values().get(size - 1) == size - 1); + assertEquals((long)ts.values().get(size - 1), size - 1); } static final Flowable<Long> infinite = Flowable.unsafeCreate(new Publisher<Long>() { @@ -249,23 +254,23 @@ public void delayErrorBuffer() { @Test public void fusedNormal() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.range(1, 10).onBackpressureBuffer().subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } @Test public void fusedError() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Flowable.<Integer>error(new TestException()).onBackpressureBuffer().subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertFailure(TestException.class); } @@ -300,11 +305,44 @@ public void emptyDelayError() { @Test public void fusionRejected() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Flowable.<Integer>never().onBackpressureBuffer().subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertEmpty(); } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestObserver<Integer> to = pp.onBackpressureBuffer(4, false, true) + .observeOn(Schedulers.io()) + .map(Functions.<Integer>identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; pp.hasSubscribers(); i++) { + pp.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java index 3be409e862..9afb864d40 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java @@ -13,11 +13,16 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.*; + import org.junit.Test; import org.reactivestreams.Publisher; import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; import io.reactivex.functions.Function; +import io.reactivex.subjects.PublishSubject; +import io.reactivex.subscribers.TestSubscriber; public class FlowableOnBackpressureErrorTest { @@ -50,4 +55,20 @@ public Object apply(Flowable<Integer> f) throws Exception { } }, false, 1, 1, 1); } + + @Test + public void overflowCancels() { + PublishSubject<Integer> ps = PublishSubject.create(); + + TestSubscriber<Integer> ts = ps.toFlowable(BackpressureStrategy.ERROR) + .test(0L); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + assertFalse(ps.hasObservers()); + + ts.assertFailure(MissingBackpressureException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java index 6fb70da88b..db6b3d9580 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatestTest.java @@ -37,6 +37,7 @@ public void testSimple() { ts.assertTerminated(); ts.assertValues(1, 2, 3, 4, 5); } + @Test public void testSimpleError() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -48,6 +49,7 @@ public void testSimpleError() { ts.assertError(TestException.class); ts.assertValues(1, 2, 3, 4, 5); } + @Test public void testSimpleBackpressure() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L); @@ -100,6 +102,7 @@ public void testSynchronousDrop() { ts.assertNoErrors(); ts.assertTerminated(); } + @Test public void testAsynchronousDrop() throws InterruptedException { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1L) { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFlowableTest.java index aeae4de368..140ca333ac 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFlowableTest.java @@ -36,10 +36,10 @@ public void testResumeNext() { TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); Flowable<String> resume = Flowable.just("twoResume", "threeResume"); - Flowable<String> observable = w.onErrorResumeNext(resume); + Flowable<String> flowable = w.onErrorResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -47,13 +47,13 @@ public void testResumeNext() { fail(e.getMessage()); } - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); } @Test @@ -78,11 +78,11 @@ public String apply(String s) { } }); - Flowable<String> observable = w.onErrorResumeNext(resume); + Flowable<String> flowable = w.onErrorResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); try { f.t.join(); @@ -90,13 +90,13 @@ public String apply(String s) { fail(e.getMessage()); } - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); } @Test @@ -111,14 +111,14 @@ public void subscribe(Subscriber<? super String> t1) { }); Flowable<String> resume = Flowable.just("resume"); - Flowable<String> observable = testObservable.onErrorResumeNext(resume); + Flowable<String> flowable = testObservable.onErrorResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("resume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("resume"); } @Test @@ -133,35 +133,34 @@ public void subscribe(Subscriber<? super String> t1) { }); Flowable<String> resume = Flowable.just("resume"); - Flowable<String> observable = testObservable.subscribeOn(Schedulers.io()).onErrorResumeNext(resume); + Flowable<String> flowable = testObservable.subscribeOn(Schedulers.io()).onErrorResumeNext(resume); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - TestSubscriber<String> ts = new TestSubscriber<String>(observer, Long.MAX_VALUE); - observable.subscribe(ts); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber, Long.MAX_VALUE); + flowable.subscribe(ts); ts.awaitTerminalEvent(); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("resume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("resume"); } static final class TestObservable implements Publisher<String> { - final Subscription s; + final Subscription upstream; final String[] values; Thread t; TestObservable(Subscription s, String... values) { - this.s = s; + this.upstream = s; this.values = values; } @Override - public void subscribe(final Subscriber<? super String> observer) { + public void subscribe(final Subscriber<? super String> subscriber) { System.out.println("TestObservable subscribed to ..."); - observer.onSubscribe(s); + subscriber.onSubscribe(upstream); t = new Thread(new Runnable() { @Override @@ -173,13 +172,13 @@ public void run() { throw new RuntimeException("Forced Failure"); } System.out.println("TestObservable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } System.out.println("TestObservable onComplete"); - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { System.out.println("TestObservable onError: " + e); - observer.onError(e); + subscriber.onError(e); } } @@ -222,15 +221,15 @@ public Integer apply(Integer t1) { public void normalBackpressure() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.onErrorResumeNext(Flowable.range(3, 2)).subscribe(ts); + pp.onErrorResumeNext(Flowable.range(3, 2)).subscribe(ts); ts.request(2); - ps.onNext(1); - ps.onNext(2); - ps.onError(new TestException("Forced failure")); + pp.onNext(1); + pp.onNext(2); + pp.onError(new TestException("Forced failure")); ts.assertValues(1, 2); ts.assertNoErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java index 68f7737943..8d3cef82d0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java @@ -40,12 +40,12 @@ public void testResumeNextWithSynchronousExecution() { Flowable<String> w = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onError(new Throwable("injected failure")); - observer.onNext("two"); - observer.onNext("three"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onError(new Throwable("injected failure")); + subscriber.onNext("two"); + subscriber.onNext("three"); } }); @@ -58,19 +58,19 @@ public Flowable<String> apply(Throwable t1) { } }; - Flowable<String> observable = w.onErrorResumeNext(resume); + Flowable<String> flowable = w.onErrorResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); assertNotNull(receivedException.get()); } @@ -88,11 +88,11 @@ public Flowable<String> apply(Throwable t1) { } }; - Flowable<String> observable = Flowable.unsafeCreate(w).onErrorResumeNext(resume); + Flowable<String> flowable = Flowable.unsafeCreate(w).onErrorResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); try { w.t.join(); @@ -100,13 +100,13 @@ public Flowable<String> apply(Throwable t1) { fail(e.getMessage()); } - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); assertNotNull(receivedException.get()); } @@ -125,11 +125,10 @@ public Flowable<String> apply(Throwable t1) { } }; - Flowable<String> observable = Flowable.unsafeCreate(w).onErrorResumeNext(resume); + Flowable<String> flowable = Flowable.unsafeCreate(w).onErrorResumeNext(resume); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { w.t.join(); @@ -138,11 +137,11 @@ public Flowable<String> apply(Throwable t1) { } // we should get the "one" value before the error - verify(observer, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("one"); // we should have received an onError call on the Observer since the resume function threw an exception - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, times(0)).onComplete(); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, times(0)).onComplete(); } /** @@ -251,7 +250,7 @@ public String apply(String s) { } }); - Flowable<String> observable = w.onErrorResumeNext(new Function<Throwable, Flowable<String>>() { + Flowable<String> flowable = w.onErrorResumeNext(new Function<Throwable, Flowable<String>>() { @Override public Flowable<String> apply(Throwable t1) { @@ -260,20 +259,19 @@ public Flowable<String> apply(Throwable t1) { }); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer, Long.MAX_VALUE); - observable.subscribe(ts); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber, Long.MAX_VALUE); + flowable.subscribe(ts); ts.awaitTerminalEvent(); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); } private static class TestFlowable implements Publisher<String> { @@ -286,9 +284,9 @@ private static class TestFlowable implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { + public void subscribe(final Subscriber<? super String> subscriber) { System.out.println("TestFlowable subscribed to ..."); - observer.onSubscribe(new BooleanSubscription()); + subscriber.onSubscribe(new BooleanSubscription()); t = new Thread(new Runnable() { @Override @@ -297,11 +295,11 @@ public void run() { System.out.println("running TestFlowable thread"); for (String s : values) { System.out.println("TestFlowable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } throw new RuntimeException("Forced Failure"); } catch (Throwable e) { - observer.onError(e); + subscriber.onError(e); } } @@ -352,9 +350,9 @@ public Integer apply(Integer t1) { public void normalBackpressure() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.onErrorResumeNext(new Function<Throwable, Flowable<Integer>>() { + pp.onErrorResumeNext(new Function<Throwable, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Throwable v) { return Flowable.range(3, 2); @@ -363,9 +361,9 @@ public Flowable<Integer> apply(Throwable v) { ts.request(2); - ps.onNext(1); - ps.onNext(2); - ps.onError(new TestException("Forced failure")); + pp.onNext(1); + pp.onNext(2); + pp.onError(new TestException("Forced failure")); ts.assertValues(1, 2); ts.assertNoErrors(); @@ -382,9 +380,9 @@ public Flowable<Integer> apply(Throwable v) { public void badOtherSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { + public Object apply(Flowable<Integer> f) throws Exception { return Flowable.error(new IOException()) - .onErrorResumeNext(Functions.justFunction(o)); + .onErrorResumeNext(Functions.justFunction(f)); } }, false, 1, 1, 1); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java index 676318f7fe..eb2f120e43 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturnTest.java @@ -38,7 +38,7 @@ public void testResumeNext() { Flowable<String> w = Flowable.unsafeCreate(f); final AtomicReference<Throwable> capturedException = new AtomicReference<Throwable>(); - Flowable<String> observable = w.onErrorReturn(new Function<Throwable, String>() { + Flowable<String> flowable = w.onErrorReturn(new Function<Throwable, String>() { @Override public String apply(Throwable e) { @@ -48,8 +48,8 @@ public String apply(Throwable e) { }); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -57,10 +57,10 @@ public String apply(Throwable e) { fail(e.getMessage()); } - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("failure"); - verify(observer, times(1)).onComplete(); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("failure"); + verify(subscriber, times(1)).onComplete(); assertNotNull(capturedException.get()); } @@ -73,7 +73,7 @@ public void testFunctionThrowsError() { Flowable<String> w = Flowable.unsafeCreate(f); final AtomicReference<Throwable> capturedException = new AtomicReference<Throwable>(); - Flowable<String> observable = w.onErrorReturn(new Function<Throwable, String>() { + Flowable<String> flowable = w.onErrorReturn(new Function<Throwable, String>() { @Override public String apply(Throwable e) { @@ -83,9 +83,8 @@ public String apply(Throwable e) { }); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -94,11 +93,11 @@ public String apply(Throwable e) { } // we should get the "one" value before the error - verify(observer, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("one"); // we should have received an onError call on the Observer since the resume function threw an exception - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, times(0)).onComplete(); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, times(0)).onComplete(); assertNotNull(capturedException.get()); } @@ -120,7 +119,7 @@ public String apply(String s) { } }); - Flowable<String> observable = w.onErrorReturn(new Function<Throwable, String>() { + Flowable<String> flowable = w.onErrorReturn(new Function<Throwable, String>() { @Override public String apply(Throwable t1) { @@ -129,18 +128,17 @@ public String apply(Throwable t1) { }); - @SuppressWarnings("unchecked") - DefaultSubscriber<String> observer = mock(DefaultSubscriber.class); - TestSubscriber<String> ts = new TestSubscriber<String>(observer, Long.MAX_VALUE); - observable.subscribe(ts); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber, Long.MAX_VALUE); + flowable.subscribe(ts); ts.awaitTerminalEvent(); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("resume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("resume"); } @Test @@ -218,9 +216,9 @@ public void run() { public void normalBackpressure() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.onErrorReturn(new Function<Throwable, Integer>() { + pp.onErrorReturn(new Function<Throwable, Integer>() { @Override public Integer apply(Throwable e) { return 3; @@ -229,9 +227,9 @@ public Integer apply(Throwable e) { ts.request(2); - ps.onNext(1); - ps.onNext(2); - ps.onError(new TestException("Forced failure")); + pp.onNext(1); + pp.onNext(2); + pp.onError(new TestException("Forced failure")); ts.assertValues(1, 2); ts.assertNoErrors(); @@ -244,7 +242,6 @@ public Integer apply(Throwable e) { ts.assertComplete(); } - @Test public void returnItem() { Flowable.error(new TestException()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java index 102c564024..ee1b4e3051 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnExceptionResumeNextViaFlowableTest.java @@ -36,10 +36,10 @@ public void testResumeNextWithException() { TestObservable f = new TestObservable("one", "EXCEPTION", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); Flowable<String> resume = Flowable.just("twoResume", "threeResume"); - Flowable<String> observable = w.onExceptionResumeNext(resume); + Flowable<String> flowable = w.onExceptionResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -47,15 +47,15 @@ public void testResumeNextWithException() { fail(e.getMessage()); } - verify(observer).onSubscribe((Subscription)any()); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -64,10 +64,10 @@ public void testResumeNextWithRuntimeException() { TestObservable f = new TestObservable("one", "RUNTIMEEXCEPTION", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); Flowable<String> resume = Flowable.just("twoResume", "threeResume"); - Flowable<String> observable = w.onExceptionResumeNext(resume); + Flowable<String> flowable = w.onExceptionResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -75,15 +75,15 @@ public void testResumeNextWithRuntimeException() { fail(e.getMessage()); } - verify(observer).onSubscribe((Subscription)any()); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -92,10 +92,10 @@ public void testThrowablePassesThru() { TestObservable f = new TestObservable("one", "THROWABLE", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); Flowable<String> resume = Flowable.just("twoResume", "threeResume"); - Flowable<String> observable = w.onExceptionResumeNext(resume); + Flowable<String> flowable = w.onExceptionResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -103,15 +103,15 @@ public void testThrowablePassesThru() { fail(e.getMessage()); } - verify(observer).onSubscribe((Subscription)any()); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, never()).onNext("twoResume"); - verify(observer, never()).onNext("threeResume"); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onNext("twoResume"); + verify(subscriber, never()).onNext("threeResume"); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -120,10 +120,10 @@ public void testErrorPassesThru() { TestObservable f = new TestObservable("one", "ERROR", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); Flowable<String> resume = Flowable.just("twoResume", "threeResume"); - Flowable<String> observable = w.onExceptionResumeNext(resume); + Flowable<String> flowable = w.onExceptionResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { f.t.join(); @@ -131,15 +131,15 @@ public void testErrorPassesThru() { fail(e.getMessage()); } - verify(observer).onSubscribe((Subscription)any()); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, never()).onNext("twoResume"); - verify(observer, never()).onNext("threeResume"); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber).onSubscribe((Subscription)any()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onNext("twoResume"); + verify(subscriber, never()).onNext("threeResume"); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -163,10 +163,10 @@ public String apply(String s) { } }); - Flowable<String> observable = w.onExceptionResumeNext(resume); + Flowable<String> flowable = w.onExceptionResumeNext(resume); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); try { // if the thread gets started (which it shouldn't if it's working correctly) @@ -177,16 +177,15 @@ public String apply(String s) { fail(e.getMessage()); } - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, times(1)).onNext("twoResume"); - verify(observer, times(1)).onNext("threeResume"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, times(1)).onNext("twoResume"); + verify(subscriber, times(1)).onNext("threeResume"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } - @Test public void testBackpressure() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); @@ -215,7 +214,6 @@ public Integer apply(Integer t1) { ts.assertNoErrors(); } - private static class TestObservable implements Publisher<String> { final String[] values; @@ -226,8 +224,8 @@ private static class TestObservable implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("TestObservable subscribed to ..."); t = new Thread(new Runnable() { @@ -246,13 +244,13 @@ public void run() { throw new Throwable("Forced Throwable"); } System.out.println("TestObservable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } System.out.println("TestObservable onComplete"); - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { System.out.println("TestObservable onError: " + e); - observer.onError(e); + subscriber.onError(e); } } @@ -267,15 +265,15 @@ public void run() { public void normalBackpressure() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.onExceptionResumeNext(Flowable.range(3, 2)).subscribe(ts); + pp.onExceptionResumeNext(Flowable.range(3, 2)).subscribe(ts); ts.request(2); - ps.onNext(1); - ps.onNext(2); - ps.onError(new TestException("Forced failure")); + pp.onNext(1); + pp.onNext(2); + pp.onError(new TestException("Forced failure")); ts.assertValues(1, 2); ts.assertNoErrors(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java new file mode 100644 index 0000000000..414aa79c07 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java @@ -0,0 +1,1629 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.*; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.HasUpstreamPublisher; +import io.reactivex.internal.operators.flowable.FlowablePublish.*; +import io.reactivex.internal.schedulers.ImmediateThinScheduler; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowablePublishAltTest { + + @Test + public void testPublish() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + ConnectableFlowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { + + @Override + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + subscriber.onNext("one"); + subscriber.onComplete(); + } + }).start(); + } + }).publish(); + + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + f.subscribe(new Consumer<String>() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + f.subscribe(new Consumer<String>() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + Disposable connection = f.connect(); + try { + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } finally { + connection.dispose(); + } + } + + @Test + public void testBackpressureFastSlow() { + ConnectableFlowable<Integer> is = Flowable.range(1, Flowable.bufferSize() * 2).publish(); + Flowable<Integer> fast = is.observeOn(Schedulers.computation()) + .doOnComplete(new Action() { + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed FAST"); + } + }); + + Flowable<Integer> slow = is.observeOn(Schedulers.computation()).map(new Function<Integer, Integer>() { + int c; + + @Override + public Integer apply(Integer i) { + if (c == 0) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + c++; + return i; + } + + }).doOnComplete(new Action() { + + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed SLOW"); + } + + }); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + Flowable.merge(fast, slow).subscribe(ts); + is.connect(); + ts.awaitTerminalEvent(); + ts.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, ts.valueCount()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStreamUsingSelector() { + final AtomicInteger emitted = new AtomicInteger(); + Flowable<Integer> xs = Flowable.range(0, Flowable.bufferSize() * 2).doOnNext(new Consumer<Integer>() { + + @Override + public void accept(Integer t1) { + emitted.incrementAndGet(); + } + + }); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + xs.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + + @Override + public Flowable<Integer> apply(Flowable<Integer> xs) { + return xs.takeUntil(xs.skipWhile(new Predicate<Integer>() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })); + } + + }).subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertNoErrors(); + ts.assertValues(0, 1, 2, 3); + assertEquals(5, emitted.get()); + System.out.println(ts.values()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStream() { + Flowable<Integer> xs = Flowable.range(0, Flowable.bufferSize() * 2); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ConnectableFlowable<Integer> xsp = xs.publish(); + xsp.takeUntil(xsp.skipWhile(new Predicate<Integer>() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })).subscribe(ts); + xsp.connect(); + System.out.println(ts.values()); + } + + @Test(timeout = 10000) + public void testBackpressureTwoConsumers() { + final AtomicInteger sourceEmission = new AtomicInteger(); + final AtomicBoolean sourceUnsubscribed = new AtomicBoolean(); + final Flowable<Integer> source = Flowable.range(1, 100) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer t1) { + sourceEmission.incrementAndGet(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + sourceUnsubscribed.set(true); + } + }).share(); + ; + + final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); + final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + if (valueCount() == 2) { + source.doOnCancel(new Action() { + @Override + public void run() { + child2Unsubscribed.set(true); + } + }).take(5).subscribe(ts2); + } + super.onNext(t); + } + }; + + source.doOnCancel(new Action() { + @Override + public void run() { + child1Unsubscribed.set(true); + } + }).take(5) + .subscribe(ts1); + + ts1.awaitTerminalEvent(); + ts2.awaitTerminalEvent(); + + ts1.assertNoErrors(); + ts2.assertNoErrors(); + + assertTrue(sourceUnsubscribed.get()); + assertTrue(child1Unsubscribed.get()); + assertTrue(child2Unsubscribed.get()); + + ts1.assertValues(1, 2, 3, 4, 5); + ts2.assertValues(4, 5, 6, 7, 8); + + assertEquals(8, sourceEmission.get()); + } + + @Test + public void testConnectWithNoSubscriber() { + TestScheduler scheduler = new TestScheduler(); + ConnectableFlowable<Long> cf = Flowable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); + cf.connect(); + // Emit 0 + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + TestSubscriber<Long> subscriber = new TestSubscriber<Long>(); + cf.subscribe(subscriber); + // Emit 1 and 2 + scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS); + subscriber.assertValues(1L, 2L); + subscriber.assertNoErrors(); + subscriber.assertTerminated(); + } + + @Test + public void testSubscribeAfterDisconnectThenConnect() { + ConnectableFlowable<Integer> source = Flowable.just(1).publish(); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + source.subscribe(ts1); + + Disposable connection = source.connect(); + + ts1.assertValue(1); + ts1.assertNoErrors(); + ts1.assertTerminated(); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + source.subscribe(ts2); + + Disposable connection2 = source.connect(); + + ts2.assertValue(1); + ts2.assertNoErrors(); + ts2.assertTerminated(); + + System.out.println(connection); + System.out.println(connection2); + } + + @Test + public void testNoSubscriberRetentionOnCompleted() { + FlowablePublish<Integer> source = (FlowablePublish<Integer>)Flowable.just(1).publish(); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + source.subscribe(ts1); + + ts1.assertNoValues(); + ts1.assertNoErrors(); + ts1.assertNotComplete(); + + source.connect(); + + ts1.assertValue(1); + ts1.assertNoErrors(); + ts1.assertTerminated(); + + assertNull(source.current.get()); + } + + @Test + public void testNonNullConnection() { + ConnectableFlowable<Object> source = Flowable.never().publish(); + + assertNotNull(source.connect()); + assertNotNull(source.connect()); + } + + @Test + public void testNoDisconnectSomeoneElse() { + ConnectableFlowable<Object> source = Flowable.never().publish(); + + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); + + connection1.dispose(); + + Disposable connection3 = source.connect(); + + connection2.dispose(); + + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); + } + + @SuppressWarnings("unchecked") + static boolean checkPublishDisposed(Disposable d) { + return ((FlowablePublish.PublishSubscriber<Object>)d).isDisposed(); + } + + @Test + public void testZeroRequested() { + ConnectableFlowable<Integer> source = Flowable.just(1).publish(); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); + + source.subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + source.connect(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + ts.request(5); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertTerminated(); + } + + @Test + public void testConnectIsIdempotent() { + final AtomicInteger calls = new AtomicInteger(); + Flowable<Integer> source = Flowable.unsafeCreate(new Publisher<Integer>() { + @Override + public void subscribe(Subscriber<? super Integer> t) { + t.onSubscribe(new BooleanSubscription()); + calls.getAndIncrement(); + } + }); + + ConnectableFlowable<Integer> conn = source.publish(); + + assertEquals(0, calls.get()); + + conn.connect(); + conn.connect(); + + assertEquals(1, calls.get()); + + conn.connect().dispose(); + + conn.connect(); + conn.connect(); + + assertEquals(2, calls.get()); + } + + @Test + public void syncFusedObserveOn() { + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).publish(); + Flowable<Integer> obs = cf.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); + for (int k = 1; k < j; k++) { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber<Integer> ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void syncFusedObserveOn2() { + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).publish(); + Flowable<Integer> obs = cf.observeOn(ImmediateThinScheduler.INSTANCE); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); + for (int k = 1; k < j; k++) { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber<Integer> ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void asyncFusedObserveOn() { + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).observeOn(ImmediateThinScheduler.INSTANCE).publish(); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); + for (int k = 1; k < j; k++) { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + tss.add(ts); + cf.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber<Integer> ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void testObserveOn() { + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).hide().publish(); + Flowable<Integer> obs = cf.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); + for (int k = 1; k < j; k++) { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber<Integer> ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void source() { + Flowable<Integer> f = Flowable.never(); + + assertSame(f, (((HasUpstreamPublisher<?>)f.publish()).source())); + } + + @Test + public void connectThrows() { + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + try { + cf.connect(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + throw new TestException(); + } + }); + } catch (TestException ex) { + // expected + } + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + + final TestSubscriber<Integer> ts = cf.test(); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.subscribe(ts2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeOnArrival() { + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + + cf.test(Long.MAX_VALUE, true).assertEmpty(); + } + + @Test + public void disposeOnArrival2() { + Flowable<Integer> co = Flowable.<Integer>never().publish().autoConnect(); + + co.test(Long.MAX_VALUE, true).assertEmpty(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.never().publish()); + + TestHelper.checkDisposed(Flowable.never().publish(Functions.<Flowable<Object>>identity())); + } + + @Test + public void empty() { + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + + cf.connect(); + } + + @Test + public void take() { + ConnectableFlowable<Integer> cf = Flowable.range(1, 2).publish(); + + TestSubscriber<Integer> ts = cf.take(1).test(); + + cf.connect(); + + ts.assertResult(1); + } + + @Test + public void just() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + ConnectableFlowable<Integer> cf = pp.publish(); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + pp.onComplete(); + } + }; + + cf.subscribe(ts); + cf.connect(); + + pp.onNext(1); + + ts.assertResult(1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final ConnectableFlowable<Integer> cf = pp.publish(); + + final TestSubscriber<Integer> ts = cf.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void badSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); + } + } + .publish() + .autoConnect() + .test() + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void noErrorLoss() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + ConnectableFlowable<Object> cf = Flowable.error(new TestException()).publish(); + + cf.connect(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void subscribeDisconnectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final ConnectableFlowable<Integer> cf = pp.publish(); + + final Disposable d = cf.connect(); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + d.dispose(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cf.subscribe(ts); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void selectorDisconnectsIndependentSource() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { + return Flowable.range(1, 2); + } + }) + .test() + .assertResult(1, 2); + + assertFalse(pp.hasSubscribers()); + } + + @Test(timeout = 5000) + public void selectorLatecommer() { + Flowable.range(1, 5) + .publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { + return v.concatWith(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .publish(Functions.<Flowable<Object>>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorInnerError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { + return Flowable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void preNextConnect() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + + cf.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.test(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void connectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.connect(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void selectorCrash() { + Flowable.just(1).publish(new Function<Flowable<Integer>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Integer> v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrows() { + Flowable.just(1) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .publish() + .autoConnect() + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrowsNoSubscribers() { + ConnectableFlowable<Integer> cf = Flowable.just(1, 2) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + if (v == 2) { + throw new TestException(); + } + return v; + } + }) + .compose(TestHelper.<Integer>flowableStripBoundary()) + .publish(); + + TestSubscriber<Integer> ts = cf.take(1) + .test(); + + cf.connect(); + + ts.assertResult(1); + } + + @Test + public void dryRunCrash() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber<Object> ts = new TestSubscriber<Object>(1L) { + @Override + public void onNext(Object t) { + super.onNext(t); + onComplete(); + cancel(); + } + }; + + Flowable.range(1, 10) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + if (v == 2) { + throw new TestException(); + } + return v; + } + }) + .publish() + .autoConnect() + .subscribe(ts); + + ts + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void overflowQueue() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> s) throws Exception { + for (int i = 0; i < 10; i++) { + s.onNext(i); + } + } + }, BackpressureStrategy.MISSING) + .publish(8) + .autoConnect() + .test(0L) + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertError(errors, 0, MissingBackpressureException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayedUpstreamOnSubscribe() { + final Subscriber<?>[] sub = { null }; + + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + sub[0] = s; + } + } + .publish() + .connect() + .dispose(); + + BooleanSubscription bs = new BooleanSubscription(); + + sub[0].onSubscribe(bs); + + assertTrue(bs.isCancelled()); + } + + @Test + public void disposeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final AtomicReference<Disposable> ref = new AtomicReference<Disposable>(); + + final ConnectableFlowable<Integer> cf = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((Disposable)s); + } + }.publish(); + + cf.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ref.get().dispose(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void removeNotPresent() { + final AtomicReference<PublishSubscriber<Integer>> ref = new AtomicReference<PublishSubscriber<Integer>>(); + + final ConnectableFlowable<Integer> cf = new Flowable<Integer>() { + @Override + @SuppressWarnings("unchecked") + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((PublishSubscriber<Integer>)s); + } + }.publish(); + + cf.connect(); + + ref.get().add(new InnerSubscriber<Integer>(new TestSubscriber<Integer>())); + ref.get().remove(null); + } + + @Test + @Ignore("publish() keeps consuming the upstream if there are no subscribers, 3.x should change this") + public void subscriberSwap() { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 5).publish(); + + cf.connect(); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + + cf.subscribe(ts1); + + ts1.assertResult(1); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + cf.subscribe(ts2); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void subscriberLiveSwap() { + final ConnectableFlowable<Integer> cf = new FlowablePublishAlt<Integer>(Flowable.range(1, 5), 128); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + cf.subscribe(ts2); + } + }; + + cf.subscribe(ts1); + + cf.connect(); + + ts1.assertResult(1); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void selectorSubscriberSwap() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + Flowable.range(1, 5).publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().take(2).test().assertResult(1, 2); + + ref.get() + .test(0) + .assertEmpty() + .requestMore(2) + .assertValuesOnly(3, 4) + .requestMore(1) + .assertResult(3, 4, 5); + } + + @Test + public void leavingSubscriberOverrequests() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + TestSubscriber<Integer> ts1 = ref.get().take(2).test(); + + pp.onNext(1); + pp.onNext(2); + + ts1.assertResult(1, 2); + + pp.onNext(3); + pp.onNext(4); + + TestSubscriber<Integer> ts2 = ref.get().test(0L); + + ts2.assertEmpty(); + + ts2.requestMore(2); + + ts2.assertValuesOnly(3, 4); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmpty() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5) + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyNotFused() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5).hide() + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyIsEmpty() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.<Integer>empty().hide() + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, calls.get()); + } + + @Test + public void publishFunctionCancelOuterAfterOneInner() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishFunctionCancelOuterAfterOneInnerBackpressured() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber<Integer>(1L) { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishCancelOneAsync() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + final TestSubscriber<Integer> ts1 = ref.get().test(); + TestSubscriber<Integer> ts2 = ref.get().test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts2.assertValuesOnly(1); + } + } + + @Test + public void publishCancelOneAsync2() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + ConnectableFlowable<Integer> cf = pp.publish(); + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + final AtomicReference<InnerSubscriber<Integer>> ref = new AtomicReference<InnerSubscriber<Integer>>(); + + cf.subscribe(new FlowableSubscriber<Integer>() { + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + ts1.onSubscribe(new BooleanSubscription()); + // pretend to be cancelled without removing it from the subscriber list + ref.set((InnerSubscriber<Integer>)s); + } + + @Override + public void onNext(Integer t) { + ts1.onNext(t); + } + + @Override + public void onError(Throwable t) { + ts1.onError(t); + } + + @Override + public void onComplete() { + ts1.onComplete(); + } + }); + TestSubscriber<Integer> ts2 = cf.test(); + + cf.connect(); + + ref.get().set(Long.MIN_VALUE); + + pp.onNext(1); + + ts1.assertEmpty(); + ts2.assertValuesOnly(1); + } + + @Test + public void boundaryFusion() { + Flowable.range(1, 10000) + .observeOn(Schedulers.single()) + .map(new Function<Integer, String>() { + @Override + public String apply(Integer t) throws Exception { + String name = Thread.currentThread().getName(); + if (name.contains("RxSingleScheduler")) { + return "RxSingleScheduler"; + } + return name; + } + }) + .share() + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.range(1, 5).publish()); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNext() { + Flowable<Integer> source = Flowable.range(0, 20) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription v) throws Exception { + System.out.println("Subscribed"); + } + }) + .publish(10) + .refCount() + ; + + Flowable<Integer> evenNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable<Integer> oddNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single<Integer> getNextOdd = oddNumbers.first(0); + + TestSubscriber<List<Integer>> ts = evenNumbers.concatMap(new Function<Integer, Publisher<List<Integer>>>() { + @Override + public Publisher<List<Integer>> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction<Integer, Integer, List<Integer>>() { + @Override + public List<Integer> apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate<List<Integer>>() { + @Override + public boolean test(List<Integer> v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNextFused() { + Flowable<Integer> source = Flowable.range(0, 20) + .publish(10) + .refCount() + ; + + Flowable<Integer> evenNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable<Integer> oddNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single<Integer> getNextOdd = oddNumbers.first(0); + + TestSubscriber<List<Integer>> ts = evenNumbers.concatMap(new Function<Integer, Publisher<List<Integer>>>() { + @Override + public Publisher<List<Integer>> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction<Integer, Integer, List<Integer>>() { + @Override + public List<Integer> apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate<List<Integer>>() { + @Override + public boolean test(List<Integer> v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } + + @Test + public void altConnectCrash() { + try { + new FlowablePublishAlt<Integer>(Flowable.<Integer>empty(), 128) + .connect(new Consumer<Disposable>() { + @Override + public void accept(Disposable t) throws Exception { + throw new TestException(); + } + }); + fail("Should have thrown"); + } catch (TestException expected) { + // expected + } + } + + @Test + public void altConnectRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ConnectableFlowable<Integer> cf = + new FlowablePublishAlt<Integer>(Flowable.<Integer>never(), 128); + + Runnable r = new Runnable() { + @Override + public void run() { + cf.connect(); + } + }; + + TestHelper.race(r, r); + } + } + + @Test + public void fusedPollCrash() { + Flowable.range(1, 5) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .publish() + .refCount() + .test() + .assertFailure(TestException.class); + } + + @Test + public void syncFusedNoRequest() { + Flowable.range(1, 5) + .publish(1) + .refCount() + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalBackpressuredPolls() { + Flowable.range(1, 5) + .hide() + .publish(1) + .refCount() + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void emptyHidden() { + Flowable.empty() + .hide() + .publish(1) + .refCount() + .test() + .assertResult(); + } + + @Test + public void emptyFused() { + Flowable.empty() + .publish(1) + .refCount() + .test() + .assertResult(); + } + + @Test + public void overflowQueueRefCount() { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + } + } + .publish(1) + .refCount() + .test(0) + .requestMore(1) + .assertFailure(MissingBackpressureException.class, 1); + } + + @Test + public void doubleErrorRefCount() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onError(new TestException("one")); + s.onError(new TestException("two")); + } + } + .publish(1) + .refCount() + .test(0) + .assertFailureAndMessage(TestException.class, "one"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "two"); + assertEquals(1, errors.size()); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java index c46f1c5aff..e744a00aa1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishFunctionTest.java @@ -33,7 +33,6 @@ import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; - public class FlowablePublishFunctionTest { @Test public void concatTakeFirstLastCompletes() { @@ -41,8 +40,8 @@ public void concatTakeFirstLastCompletes() { Flowable.range(1, 3).publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return Flowable.concat(o.take(5), o.takeLast(5)); + public Flowable<Integer> apply(Flowable<Integer> f) { + return Flowable.concat(f.take(5), f.takeLast(5)); } }).subscribe(ts); @@ -57,8 +56,8 @@ public void concatTakeFirstLastBackpressureCompletes() { Flowable.range(1, 6).publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return Flowable.concat(o.take(5), o.takeLast(5)); + public Flowable<Integer> apply(Flowable<Integer> f) { + return Flowable.concat(f.take(5), f.takeLast(5)); } }).subscribe(ts); @@ -84,17 +83,17 @@ public Flowable<Integer> apply(Flowable<Integer> o) { public void canBeCancelled() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return Flowable.concat(o.take(5), o.takeLast(5)); + public Flowable<Integer> apply(Flowable<Integer> f) { + return Flowable.concat(f.take(5), f.takeLast(5)); } }).subscribe(ts); - ps.onNext(1); - ps.onNext(2); + pp.onNext(1); + pp.onNext(2); ts.assertValues(1, 2); ts.assertNoErrors(); @@ -102,7 +101,7 @@ public Flowable<Integer> apply(Flowable<Integer> o) { ts.cancel(); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @Test @@ -120,22 +119,22 @@ public void invalidPrefetch() { public void takeCompletes() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o.take(1); + public Flowable<Integer> apply(Flowable<Integer> f) { + return f.take(1); } }).subscribe(ts); - ps.onNext(1); + pp.onNext(1); ts.assertValues(1); ts.assertNoErrors(); ts.assertComplete(); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @@ -151,12 +150,12 @@ public void onStart() { } }; - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o.take(1); + public Flowable<Integer> apply(Flowable<Integer> f) { + return f.take(1); } }).subscribe(ts); @@ -167,62 +166,62 @@ public Flowable<Integer> apply(Flowable<Integer> o) { public void takeCompletesUnsafe() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o.take(1); + public Flowable<Integer> apply(Flowable<Integer> f) { + return f.take(1); } }).subscribe(ts); - ps.onNext(1); + pp.onNext(1); ts.assertValues(1); ts.assertNoErrors(); ts.assertComplete(); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @Test public void directCompletesUnsafe() { TestSubscriber<Integer> ts = TestSubscriber.create(); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o; + public Flowable<Integer> apply(Flowable<Integer> f) { + return f; } }).subscribe(ts); - ps.onNext(1); - ps.onComplete(); + pp.onNext(1); + pp.onComplete(); ts.assertValues(1); ts.assertNoErrors(); ts.assertComplete(); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @Test public void overflowMissingBackpressureException() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o; + public Flowable<Integer> apply(Flowable<Integer> f) { + return f; } }).subscribe(ts); for (int i = 0; i < Flowable.bufferSize() * 2; i++) { - ps.onNext(i); + pp.onNext(i); } ts.assertNoValues(); @@ -231,24 +230,24 @@ public Flowable<Integer> apply(Flowable<Integer> o) { Assert.assertEquals("Could not emit value due to lack of requests", ts.errors().get(0).getMessage()); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @Test public void overflowMissingBackpressureExceptionDelayed() { TestSubscriber<Integer> ts = TestSubscriber.create(0); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - new FlowablePublishMulticast<Integer, Integer>(ps, new Function<Flowable<Integer>, Flowable<Integer>>() { + new FlowablePublishMulticast<Integer, Integer>(pp, new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o; + public Flowable<Integer> apply(Flowable<Integer> f) { + return f; } }, Flowable.bufferSize(), true).subscribe(ts); for (int i = 0; i < Flowable.bufferSize() * 2; i++) { - ps.onNext(i); + pp.onNext(i); } ts.request(Flowable.bufferSize()); @@ -258,7 +257,7 @@ public Flowable<Integer> apply(Flowable<Integer> o) { ts.assertNotComplete(); Assert.assertEquals("Could not emit value due to lack of requests", ts.errors().get(0).getMessage()); - Assert.assertFalse("Source has subscribers?", ps.hasSubscribers()); + Assert.assertFalse("Source has subscribers?", pp.hasSubscribers()); } @Test @@ -427,7 +426,7 @@ public void inputOutputSubscribeRace2() { @Test public void sourceSubscriptionDelayed() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(0L); Flowable.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java new file mode 100644 index 0000000000..4078b5b0b0 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticastTest.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import io.reactivex.TestHelper; +import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.operators.flowable.FlowablePublishMulticast.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowablePublishMulticastTest { + + @Test + public void asyncFusedInput() { + MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + UnicastProcessor<Integer> up = UnicastProcessor.create(); + + up.subscribe(mp); + + TestSubscriber<Integer> ts1 = mp.test(); + TestSubscriber<Integer> ts2 = mp.take(1).test(); + + up.onNext(1); + up.onNext(2); + up.onComplete(); + + ts1.assertResult(1, 2); + ts2.assertResult(1); + } + + @Test + public void fusionRejectedInput() { + MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + mp.onSubscribe(new QueueSubscription<Integer>() { + + @Override + public int requestFusion(int mode) { + return 0; + } + + @Override + public boolean offer(Integer value) { + return false; + } + + @Override + public boolean offer(Integer v1, Integer v2) { + return false; + } + + @Override + public Integer poll() throws Exception { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void clear() { + } + + @Override + public void request(long n) { + } + + @Override + public void cancel() { + } + }); + + TestSubscriber<Integer> ts = mp.test(); + + mp.onNext(1); + mp.onNext(2); + mp.onComplete(); + + ts.assertResult(1, 2); + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + final MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + final MulticastSubscription<Integer> ms2 = new MulticastSubscription<Integer>(null, mp); + + assertTrue(mp.add(ms1)); + + Runnable r1 = new Runnable() { + @Override + public void run() { + mp.remove(ms1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + mp.add(ms2); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void removeNotFound() { + MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + assertTrue(mp.add(ms1)); + + mp.remove(null); + } + + @Test + public void errorAllCancelled() { + MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + assertTrue(mp.add(ms1)); + + ms1.set(Long.MIN_VALUE); + + mp.errorAll(null); + } + + @Test + public void completeAllCancelled() { + MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + assertTrue(mp.add(ms1)); + + ms1.set(Long.MIN_VALUE); + + mp.completeAll(); + } + + @Test + public void cancelledWhileFindingRequests() { + final MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + final MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + + assertTrue(mp.add(ms1)); + + mp.onSubscribe(new BooleanSubscription()); + + ms1.set(Long.MIN_VALUE); + + mp.drain(); + } + + @Test + public void negativeRequest() { + final MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + final MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(null, mp); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + ms1.request(-1); + + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void outputCancellerDoubleOnSubscribe() { + TestHelper.doubleOnSubscribe(new OutputCanceller<Object>(new TestSubscriber<Object>(), null)); + } + + @Test + public void dontDropItemsWhenNoReadyConsumers() { + final MulticastProcessor<Integer> mp = new MulticastProcessor<Integer>(128, true); + + mp.onSubscribe(new BooleanSubscription()); + + mp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + final MulticastSubscription<Integer> ms1 = new MulticastSubscription<Integer>(ts, mp); + ts.onSubscribe(ms1); + + assertTrue(mp.add(ms1)); + + ms1.set(Long.MIN_VALUE); + + mp.drain(); + + assertFalse(mp.queue.isEmpty()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java index 79305861ad..80af00c66f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; @@ -29,6 +29,7 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.HasUpstreamPublisher; +import io.reactivex.internal.operators.flowable.FlowablePublish.*; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; @@ -38,21 +39,43 @@ public class FlowablePublishTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableFlowableAssembly(new Function<ConnectableFlowable, ConnectableFlowable>() { + @Override + public ConnectableFlowable apply(ConnectableFlowable co) throws Exception { + if (co instanceof FlowablePublishAlt) { + FlowablePublishAlt fpa = (FlowablePublishAlt) co; + return FlowablePublish.create(Flowable.fromPublisher(fpa.source()), fpa.publishBufferSize()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableFlowableAssembly(null); + } + @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableFlowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + ConnectableFlowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } @@ -61,7 +84,7 @@ public void run() { final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { @@ -71,7 +94,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { @@ -80,14 +103,14 @@ public void accept(String v) { } }); - Disposable s = o.connect(); + Disposable connection = f.connect(); try { if (!latch.await(1000, TimeUnit.MILLISECONDS)) { fail("subscriptions did not receive values"); } assertEquals(1, counter.get()); } finally { - s.dispose(); + connection.dispose(); } } @@ -253,12 +276,12 @@ public void run() { @Test public void testConnectWithNoSubscriber() { TestScheduler scheduler = new TestScheduler(); - ConnectableFlowable<Long> co = Flowable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); - co.connect(); + ConnectableFlowable<Long> cf = Flowable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); + cf.connect(); // Emit 0 scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); TestSubscriber<Long> subscriber = new TestSubscriber<Long>(); - co.subscribe(subscriber); + cf.subscribe(subscriber); // Emit 1 and 2 scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS); subscriber.assertValues(1L, 2L); @@ -274,7 +297,7 @@ public void testSubscribeAfterDisconnectThenConnect() { source.subscribe(ts1); - Disposable s = source.connect(); + Disposable connection = source.connect(); ts1.assertValue(1); ts1.assertNoErrors(); @@ -284,14 +307,14 @@ public void testSubscribeAfterDisconnectThenConnect() { source.subscribe(ts2); - Disposable s2 = source.connect(); + Disposable connection2 = source.connect(); ts2.assertValue(1); ts2.assertNoErrors(); ts2.assertTerminated(); - System.out.println(s); - System.out.println(s2); + System.out.println(connection); + System.out.println(connection2); } @Test @@ -327,18 +350,18 @@ public void testNonNullConnection() { public void testNoDisconnectSomeoneElse() { ConnectableFlowable<Object> source = Flowable.never().publish(); - Disposable s1 = source.connect(); - Disposable s2 = source.connect(); + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); - s1.dispose(); + connection1.dispose(); - Disposable s3 = source.connect(); + Disposable connection3 = source.connect(); - s2.dispose(); + connection2.dispose(); - assertTrue(checkPublishDisposed(s1)); - assertTrue(checkPublishDisposed(s2)); - assertFalse(checkPublishDisposed(s3)); + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); } @SuppressWarnings("unchecked") @@ -370,6 +393,7 @@ public void testZeroRequested() { ts.assertNoErrors(); ts.assertTerminated(); } + @Test public void testConnectIsIdempotent() { final AtomicInteger calls = new AtomicInteger(); @@ -400,8 +424,8 @@ public void subscribe(Subscriber<? super Integer> t) { @Test public void syncFusedObserveOn() { - ConnectableFlowable<Integer> co = Flowable.range(0, 1000).publish(); - Flowable<Integer> obs = co.observeOn(Schedulers.computation()); + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).publish(); + Flowable<Integer> obs = cf.observeOn(Schedulers.computation()); for (int i = 0; i < 1000; i++) { for (int j = 1; j < 6; j++) { List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); @@ -411,7 +435,7 @@ public void syncFusedObserveOn() { obs.subscribe(ts); } - Disposable s = co.connect(); + Disposable connection = cf.connect(); for (TestSubscriber<Integer> ts : tss) { ts.awaitDone(5, TimeUnit.SECONDS) @@ -420,15 +444,15 @@ public void syncFusedObserveOn() { .assertNoErrors() .assertComplete(); } - s.dispose(); + connection.dispose(); } } } @Test public void syncFusedObserveOn2() { - ConnectableFlowable<Integer> co = Flowable.range(0, 1000).publish(); - Flowable<Integer> obs = co.observeOn(ImmediateThinScheduler.INSTANCE); + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).publish(); + Flowable<Integer> obs = cf.observeOn(ImmediateThinScheduler.INSTANCE); for (int i = 0; i < 1000; i++) { for (int j = 1; j < 6; j++) { List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); @@ -438,7 +462,7 @@ public void syncFusedObserveOn2() { obs.subscribe(ts); } - Disposable s = co.connect(); + Disposable connection = cf.connect(); for (TestSubscriber<Integer> ts : tss) { ts.awaitDone(5, TimeUnit.SECONDS) @@ -447,24 +471,24 @@ public void syncFusedObserveOn2() { .assertNoErrors() .assertComplete(); } - s.dispose(); + connection.dispose(); } } } @Test public void asyncFusedObserveOn() { - ConnectableFlowable<Integer> co = Flowable.range(0, 1000).observeOn(ImmediateThinScheduler.INSTANCE).publish(); + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).observeOn(ImmediateThinScheduler.INSTANCE).publish(); for (int i = 0; i < 1000; i++) { for (int j = 1; j < 6; j++) { List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); for (int k = 1; k < j; k++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); tss.add(ts); - co.subscribe(ts); + cf.subscribe(ts); } - Disposable s = co.connect(); + Disposable connection = cf.connect(); for (TestSubscriber<Integer> ts : tss) { ts.awaitDone(5, TimeUnit.SECONDS) @@ -473,15 +497,15 @@ public void asyncFusedObserveOn() { .assertNoErrors() .assertComplete(); } - s.dispose(); + connection.dispose(); } } } @Test public void testObserveOn() { - ConnectableFlowable<Integer> co = Flowable.range(0, 1000).hide().publish(); - Flowable<Integer> obs = co.observeOn(Schedulers.computation()); + ConnectableFlowable<Integer> cf = Flowable.range(0, 1000).hide().publish(); + Flowable<Integer> obs = cf.observeOn(Schedulers.computation()); for (int i = 0; i < 1000; i++) { for (int j = 1; j < 6; j++) { List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>(); @@ -491,7 +515,7 @@ public void testObserveOn() { obs.subscribe(ts); } - Disposable s = co.connect(); + Disposable connection = cf.connect(); for (TestSubscriber<Integer> ts : tss) { ts.awaitDone(5, TimeUnit.SECONDS) @@ -500,25 +524,25 @@ public void testObserveOn() { .assertNoErrors() .assertComplete(); } - s.dispose(); + connection.dispose(); } } } @Test public void source() { - Flowable<Integer> o = Flowable.never(); + Flowable<Integer> f = Flowable.never(); - assertSame(o, (((HasUpstreamPublisher<?>)o.publish()).source())); + assertSame(f, (((HasUpstreamPublisher<?>)f.publish()).source())); } @Test public void connectThrows() { - ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); try { - co.connect(new Consumer<Disposable>() { + cf.connect(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException(); } }); @@ -529,25 +553,25 @@ public void accept(Disposable s) throws Exception { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); - final TestSubscriber<Integer> to = co.test(); + final TestSubscriber<Integer> ts = cf.test(); - final TestSubscriber<Integer> to2 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { - co.subscribe(to2); + cf.subscribe(ts2); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -557,9 +581,9 @@ public void run() { @Test public void disposeOnArrival() { - ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); - co.test(Long.MAX_VALUE, true).assertEmpty(); + cf.test(Long.MAX_VALUE, true).assertEmpty(); } @Test @@ -578,65 +602,65 @@ public void dispose() { @Test public void empty() { - ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); - co.connect(); + cf.connect(); } @Test public void take() { - ConnectableFlowable<Integer> co = Flowable.range(1, 2).publish(); + ConnectableFlowable<Integer> cf = Flowable.range(1, 2).publish(); - TestSubscriber<Integer> to = co.take(1).test(); + TestSubscriber<Integer> ts = cf.take(1).test(); - co.connect(); + cf.connect(); - to.assertResult(1); + ts.assertResult(1); } @Test public void just() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - ConnectableFlowable<Integer> co = ps.publish(); + ConnectableFlowable<Integer> cf = pp.publish(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); - ps.onComplete(); + pp.onComplete(); } }; - co.subscribe(to); - co.connect(); + cf.subscribe(ts); + cf.connect(); - ps.onNext(1); + pp.onNext(1); - to.assertResult(1); + ts.assertResult(1); } @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final ConnectableFlowable<Integer> co = ps.publish(); + final ConnectableFlowable<Integer> cf = pp.publish(); - final TestSubscriber<Integer> to = co.test(); + final TestSubscriber<Integer> ts = cf.test(); Runnable r1 = new Runnable() { @Override public void run() { - ps.onNext(1); + pp.onNext(1); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -650,13 +674,13 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onComplete(); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .publish() @@ -674,9 +698,9 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { public void noErrorLoss() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - ConnectableFlowable<Object> co = Flowable.error(new TestException()).publish(); + ConnectableFlowable<Object> cf = Flowable.error(new TestException()).publish(); - co.connect(); + cf.connect(); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { @@ -686,14 +710,14 @@ public void noErrorLoss() { @Test public void subscribeDisconnectRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final ConnectableFlowable<Integer> co = ps.publish(); + final ConnectableFlowable<Integer> cf = pp.publish(); - final Disposable d = co.connect(); - final TestSubscriber<Integer> to = new TestSubscriber<Integer>(); + final Disposable d = cf.connect(); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Runnable r1 = new Runnable() { @Override @@ -705,7 +729,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - co.subscribe(to); + cf.subscribe(ts); } }; @@ -715,9 +739,9 @@ public void run() { @Test public void selectorDisconnectsIndependentSource() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return Flowable.range(1, 2); @@ -726,7 +750,7 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { .test() .assertResult(1, 2); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test(timeout = 5000) @@ -752,9 +776,9 @@ public void mainError() { @Test public void selectorInnerError() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { + pp.publish(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return Flowable.error(new TestException()); @@ -763,21 +787,21 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { .test() .assertFailure(TestException.class); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); } @Test public void preNextConnect() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); - co.connect(); + cf.connect(); Runnable r1 = new Runnable() { @Override public void run() { - co.test(); + cf.test(); } }; @@ -787,14 +811,14 @@ public void run() { @Test public void connectRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish(); + final ConnectableFlowable<Integer> cf = Flowable.<Integer>empty().publish(); Runnable r1 = new Runnable() { @Override public void run() { - co.connect(); + cf.connect(); } }; @@ -823,6 +847,7 @@ public Object apply(Integer v) throws Exception { throw new TestException(); } }) + .compose(TestHelper.flowableStripBoundary()) .publish() .autoConnect() .test() @@ -830,48 +855,85 @@ public Object apply(Integer v) throws Exception { } @Test - public void dryRunCrash() { - final TestSubscriber<Object> ts = new TestSubscriber<Object>(1L) { + public void pollThrowsNoSubscribers() { + ConnectableFlowable<Integer> cf = Flowable.just(1, 2) + .map(new Function<Integer, Integer>() { @Override - public void onNext(Object t) { - super.onNext(t); - onComplete(); - cancel(); - } - }; - - Flowable.range(1, 10) - .map(new Function<Integer, Object>() { - @Override - public Object apply(Integer v) throws Exception { + public Integer apply(Integer v) throws Exception { if (v == 2) { throw new TestException(); } return v; } }) - .publish() - .autoConnect() - .subscribe(ts); + .compose(TestHelper.<Integer>flowableStripBoundary()) + .publish(); - ts - .assertResult(1); + TestSubscriber<Integer> ts = cf.take(1) + .test(); + + cf.connect(); + + ts.assertResult(1); + } + + @Test + public void dryRunCrash() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber<Object> ts = new TestSubscriber<Object>(1L) { + @Override + public void onNext(Object t) { + super.onNext(t); + onComplete(); + cancel(); + } + }; + + Flowable.range(1, 10) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + if (v == 2) { + throw new TestException(); + } + return v; + } + }) + .publish() + .autoConnect() + .subscribe(ts); + + ts + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void overflowQueue() { - Flowable.create(new FlowableOnSubscribe<Object>() { - @Override - public void subscribe(FlowableEmitter<Object> s) throws Exception { - for (int i = 0; i < 10; i++) { - s.onNext(i); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> s) throws Exception { + for (int i = 0; i < 10; i++) { + s.onNext(i); + } } - } - }, BackpressureStrategy.MISSING) - .publish(8) - .autoConnect() - .test(0L) - .assertFailure(MissingBackpressureException.class); + }, BackpressureStrategy.MISSING) + .publish(8) + .autoConnect() + .test(0L) + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertError(errors, 0, MissingBackpressureException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -894,4 +956,568 @@ protected void subscribeActual(Subscriber<? super Integer> s) { assertTrue(bs.isCancelled()); } + + @Test + public void disposeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final AtomicReference<Disposable> ref = new AtomicReference<Disposable>(); + + final ConnectableFlowable<Integer> cf = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((Disposable)s); + } + }.publish(); + + cf.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ref.get().dispose(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void removeNotPresent() { + final AtomicReference<PublishSubscriber<Integer>> ref = new AtomicReference<PublishSubscriber<Integer>>(); + + final ConnectableFlowable<Integer> cf = new Flowable<Integer>() { + @Override + @SuppressWarnings("unchecked") + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((PublishSubscriber<Integer>)s); + } + }.publish(); + + cf.connect(); + + ref.get().add(new InnerSubscriber<Integer>(new TestSubscriber<Integer>())); + ref.get().remove(null); + } + + @Test + @Ignore("publish() keeps consuming the upstream if there are no subscribers, 3.x should change this") + public void subscriberSwap() { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 5).publish(); + + cf.connect(); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + + cf.subscribe(ts1); + + ts1.assertResult(1); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + cf.subscribe(ts2); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void subscriberLiveSwap() { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 5).publish(); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + cf.subscribe(ts2); + } + }; + + cf.subscribe(ts1); + + cf.connect(); + + ts1.assertResult(1); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void selectorSubscriberSwap() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + Flowable.range(1, 5).publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().take(2).test().assertResult(1, 2); + + ref.get() + .test(0) + .assertEmpty() + .requestMore(2) + .assertValuesOnly(3, 4) + .requestMore(1) + .assertResult(3, 4, 5); + } + + @Test + public void leavingSubscriberOverrequests() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + TestSubscriber<Integer> ts1 = ref.get().take(2).test(); + + pp.onNext(1); + pp.onNext(2); + + ts1.assertResult(1, 2); + + pp.onNext(3); + pp.onNext(4); + + TestSubscriber<Integer> ts2 = ref.get().test(0L); + + ts2.assertEmpty(); + + ts2.requestMore(2); + + ts2.assertValuesOnly(3, 4); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmpty() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5) + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyNotFused() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5).hide() + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyIsEmpty() { + final FlowableTransformer<Integer, Integer> transformer = new FlowableTransformer<Integer, Integer>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> g) { + return g.map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.<Integer>empty().hide() + .publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(final Flowable<Integer> shared) + throws Exception { + return shared.take(1).concatMap(new Function<Integer, Publisher<? extends Integer>>() { + @Override + public Publisher<? extends Integer> apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, calls.get()); + } + + @Test + public void publishFunctionCancelOuterAfterOneInner() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishFunctionCancelOuterAfterOneInnerBackpressured() { + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber<Integer>(1L) { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishCancelOneAsync() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final AtomicReference<Flowable<Integer>> ref = new AtomicReference<Flowable<Integer>>(); + + pp.publish(new Function<Flowable<Integer>, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Flowable<Integer> f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + final TestSubscriber<Integer> ts1 = ref.get().test(); + TestSubscriber<Integer> ts2 = ref.get().test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts2.assertValuesOnly(1); + } + } + + @Test + public void publishCancelOneAsync2() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + ConnectableFlowable<Integer> cf = pp.publish(); + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + final AtomicReference<InnerSubscriber<Integer>> ref = new AtomicReference<InnerSubscriber<Integer>>(); + + cf.subscribe(new FlowableSubscriber<Integer>() { + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + ts1.onSubscribe(new BooleanSubscription()); + // pretend to be cancelled without removing it from the subscriber list + ref.set((InnerSubscriber<Integer>)s); + } + + @Override + public void onNext(Integer t) { + ts1.onNext(t); + } + + @Override + public void onError(Throwable t) { + ts1.onError(t); + } + + @Override + public void onComplete() { + ts1.onComplete(); + } + }); + TestSubscriber<Integer> ts2 = cf.test(); + + cf.connect(); + + ref.get().set(Long.MIN_VALUE); + + pp.onNext(1); + + ts1.assertEmpty(); + ts2.assertValuesOnly(1); + } + + @Test + public void boundaryFusion() { + Flowable.range(1, 10000) + .observeOn(Schedulers.single()) + .map(new Function<Integer, String>() { + @Override + public String apply(Integer t) throws Exception { + String name = Thread.currentThread().getName(); + if (name.contains("RxSingleScheduler")) { + return "RxSingleScheduler"; + } + return name; + } + }) + .share() + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.range(1, 5).publish()); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNext() { + Flowable<Integer> source = Flowable.range(0, 20) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription v) throws Exception { + System.out.println("Subscribed"); + } + }) + .publish(10) + .refCount() + ; + + Flowable<Integer> evenNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable<Integer> oddNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single<Integer> getNextOdd = oddNumbers.first(0); + + TestSubscriber<List<Integer>> ts = evenNumbers.concatMap(new Function<Integer, Publisher<List<Integer>>>() { + @Override + public Publisher<List<Integer>> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction<Integer, Integer, List<Integer>>() { + @Override + public List<Integer> apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate<List<Integer>>() { + @Override + public boolean test(List<Integer> v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNextFused() { + Flowable<Integer> source = Flowable.range(0, 20) + .publish(10) + .refCount() + ; + + Flowable<Integer> evenNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable<Integer> oddNumbers = source.filter(new Predicate<Integer>() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single<Integer> getNextOdd = oddNumbers.first(0); + + TestSubscriber<List<Integer>> ts = evenNumbers.concatMap(new Function<Integer, Publisher<List<Integer>>>() { + @Override + public Publisher<List<Integer>> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction<Integer, Integer, List<Integer>>() { + @Override + public List<Integer> apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate<List<Integer>>() { + @Override + public boolean test(List<Integer> v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java index 2fbd1be6c3..e3d67bb8a2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -26,28 +25,28 @@ import io.reactivex.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.subscribers.*; public class FlowableRangeLongTest { @Test public void testRangeStartAt2Count3() { - Subscriber<Long> observer = TestHelper.mockSubscriber(); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); - Flowable.rangeLong(2, 3).subscribe(observer); + Flowable.rangeLong(2, 3).subscribe(subscriber); - verify(observer, times(1)).onNext(2L); - verify(observer, times(1)).onNext(3L); - verify(observer, times(1)).onNext(4L); - verify(observer, never()).onNext(5L); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(2L); + verify(subscriber, times(1)).onNext(3L); + verify(subscriber, times(1)).onNext(4L); + verify(subscriber, never()).onNext(5L); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testRangeUnsubscribe() { - Subscriber<Long> observer = TestHelper.mockSubscriber(); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); final AtomicInteger count = new AtomicInteger(); @@ -57,14 +56,14 @@ public void accept(Long t1) { count.incrementAndGet(); } }) - .take(3).subscribe(observer); - - verify(observer, times(1)).onNext(1L); - verify(observer, times(1)).onNext(2L); - verify(observer, times(1)).onNext(3L); - verify(observer, never()).onNext(4L); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + .take(3).subscribe(subscriber); + + verify(subscriber, times(1)).onNext(1L); + verify(subscriber, times(1)).onNext(2L); + verify(subscriber, times(1)).onNext(3L); + verify(subscriber, never()).onNext(4L); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); assertEquals(3, count.get()); } @@ -95,14 +94,14 @@ public void testRangeWithOverflow5() { @Test public void testBackpressureViaRequest() { - Flowable<Long> o = Flowable.rangeLong(1, Flowable.bufferSize()); + Flowable<Long> f = Flowable.rangeLong(1, Flowable.bufferSize()); TestSubscriber<Long> ts = new TestSubscriber<Long>(0L); ts.assertNoValues(); ts.request(1); - o.subscribe(ts); + f.subscribe(ts); ts.assertValue(1L); @@ -123,14 +122,14 @@ public void testNoBackpressure() { list.add(i); } - Flowable<Long> o = Flowable.rangeLong(1, list.size()); + Flowable<Long> f = Flowable.rangeLong(1, list.size()); TestSubscriber<Long> ts = new TestSubscriber<Long>(0L); ts.assertNoValues(); ts.request(Long.MAX_VALUE); // infinite - o.subscribe(ts); + f.subscribe(ts); ts.assertValueSequence(list); ts.assertTerminated(); @@ -164,18 +163,21 @@ void testWithBackpressureAllAtOnce(long start) { ts.assertValueSequence(list); ts.assertTerminated(); } + @Test public void testWithBackpressure1() { for (long i = 0; i < 100; i++) { testWithBackpressureOneByOne(i); } } + @Test public void testWithBackpressureAllAtOnce() { for (long i = 0; i < 100; i++) { testWithBackpressureAllAtOnce(i); } } + @Test public void testWithBackpressureRequestWayMore() { Flowable<Long> source = Flowable.rangeLong(50, 100); @@ -290,21 +292,21 @@ public void countOne() { @Test public void fused() { - TestSubscriber<Long> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Long> ts = SubscriberFusion.newTest(QueueFuseable.ANY); - Flowable.rangeLong(1, 2).subscribe(to); + Flowable.rangeLong(1, 2).subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1L, 2L); } @Test public void fusedReject() { - TestSubscriber<Long> to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + TestSubscriber<Long> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); - Flowable.rangeLong(1, 2).subscribe(to); + Flowable.rangeLong(1, 2).subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1L, 2L); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java index 86dd5dbb58..3772aefd85 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -26,28 +25,28 @@ import io.reactivex.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.subscribers.*; public class FlowableRangeTest { @Test public void testRangeStartAt2Count3() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - Flowable.range(2, 3).subscribe(observer); + Flowable.range(2, 3).subscribe(subscriber); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onNext(4); - verify(observer, never()).onNext(5); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, times(1)).onNext(4); + verify(subscriber, never()).onNext(5); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testRangeUnsubscribe() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); final AtomicInteger count = new AtomicInteger(); @@ -57,14 +56,14 @@ public void accept(Integer t1) { count.incrementAndGet(); } }) - .take(3).subscribe(observer); - - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, never()).onNext(4); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + .take(3).subscribe(subscriber); + + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, never()).onNext(4); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); assertEquals(3, count.get()); } @@ -95,14 +94,14 @@ public void testRangeWithOverflow5() { @Test public void testBackpressureViaRequest() { - Flowable<Integer> o = Flowable.range(1, Flowable.bufferSize()); + Flowable<Integer> f = Flowable.range(1, Flowable.bufferSize()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); ts.assertNoValues(); ts.request(1); - o.subscribe(ts); + f.subscribe(ts); ts.assertValue(1); @@ -123,14 +122,14 @@ public void testNoBackpressure() { list.add(i); } - Flowable<Integer> o = Flowable.range(1, list.size()); + Flowable<Integer> f = Flowable.range(1, list.size()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); ts.assertNoValues(); ts.request(Long.MAX_VALUE); // infinite - o.subscribe(ts); + f.subscribe(ts); ts.assertValueSequence(list); ts.assertTerminated(); @@ -164,18 +163,21 @@ void testWithBackpressureAllAtOnce(int start) { ts.assertValueSequence(list); ts.assertTerminated(); } + @Test public void testWithBackpressure1() { for (int i = 0; i < 100; i++) { testWithBackpressureOneByOne(i); } } + @Test public void testWithBackpressureAllAtOnce() { for (int i = 0; i < 100; i++) { testWithBackpressureAllAtOnce(i); } } + @Test public void testWithBackpressureRequestWayMore() { Flowable<Integer> source = Flowable.range(50, 100); @@ -260,6 +262,7 @@ public void testNearMaxValueWithoutBackpressure() { ts.assertNoErrors(); ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } + @Test(timeout = 1000) public void testNearMaxValueWithBackpressure() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(3L); @@ -270,7 +273,6 @@ public void testNearMaxValueWithBackpressure() { ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } - @Test public void negativeCount() { try { @@ -283,12 +285,12 @@ public void negativeCount() { @Test public void requestWrongFusion() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); Flowable.range(1, 5) - .subscribe(to); + .subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); } @@ -301,21 +303,21 @@ public void countOne() { @Test public void fused() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); - Flowable.range(1, 2).subscribe(to); + Flowable.range(1, 2).subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) + SubscriberFusion.assertFusion(ts, QueueFuseable.SYNC) .assertResult(1, 2); } @Test public void fusedReject() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ASYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ASYNC); - Flowable.range(1, 2).subscribe(to); + Flowable.range(1, 2).subscribe(ts); - SubscriberFusion.assertFusion(to, QueueDisposable.NONE) + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertResult(1, 2); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java index e50d8faa57..af88de841f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -29,17 +28,18 @@ import io.reactivex.internal.fuseable.HasUpstreamPublisher; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableReduceTest { - Subscriber<Object> observer; + Subscriber<Object> subscriber; SingleObserver<Object> singleObserver; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); singleObserver = TestHelper.mockSingleObserver(); } @@ -61,11 +61,11 @@ public Integer apply(Integer v) { } }); - result.subscribe(observer); + result.subscribe(subscriber); - verify(observer).onNext(1 + 2 + 3 + 4 + 5); - verify(observer).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1 + 2 + 3 + 4 + 5); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -79,11 +79,11 @@ public Integer apply(Integer v) { } }); - result.subscribe(observer); + result.subscribe(subscriber); - verify(observer, never()).onNext(any()); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(any(TestException.class)); } @Test @@ -103,11 +103,11 @@ public Integer apply(Integer v) { } }); - result.subscribe(observer); + result.subscribe(subscriber); - verify(observer, never()).onNext(any()); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(any(TestException.class)); } @Test @@ -124,11 +124,11 @@ public Integer apply(Integer t1) { Flowable<Integer> result = Flowable.just(1, 2, 3, 4, 5) .reduce(0, sum).toFlowable().map(error); - result.subscribe(observer); + result.subscribe(subscriber); - verify(observer, never()).onNext(any()); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(any(TestException.class)); } @Test @@ -140,7 +140,6 @@ public void testBackpressureWithInitialValueFlowable() throws InterruptedExcepti assertEquals(21, r.intValue()); } - @Test public void testAggregateAsIntSum() { @@ -388,7 +387,8 @@ public Integer apply(Integer a, Integer b) throws Exception { } /** - * https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 + * Make sure an asynchronous reduce with flatMap works. + * Original Reactor-Core test case: https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 */ @Test public void shouldReduceTo10Events() { @@ -414,7 +414,8 @@ public String apply(String l, String r) throws Exception { @Override public void accept(String s) throws Exception { count.incrementAndGet(); - System.out.println("Completed with " + s);} + System.out.println("Completed with " + s); + } }) .toFlowable(); } @@ -425,7 +426,8 @@ public void accept(String s) throws Exception { } /** - * https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 + * Make sure an asynchronous reduce with flatMap works. + * Original Reactor-Core test case: https://gist.github.com/jurna/353a2bd8ff83f0b24f0b5bc772077d61 */ @Test public void shouldReduceTo10EventsFlowable() { @@ -452,7 +454,8 @@ public String apply(String l, String r) throws Exception { @Override public void accept(String s) throws Exception { count.incrementAndGet(); - System.out.println("Completed with " + s);} + System.out.println("Completed with " + s); + } }) ; } @@ -470,4 +473,59 @@ static String blockingOp(Integer x, Integer y) { } return "x" + x + "y" + y; } + + @Test + public void seedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Integer>, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Flowable<Integer> f) + throws Exception { + return f.reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + }); + } + }); + } + + @Test + public void seedDisposed() { + TestHelper.checkDisposed(PublishProcessor.<Integer>create().reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + })); + } + + @Test + public void seedBadSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); + } + } + .reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + }) + .test() + .assertResult(0); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java new file mode 100644 index 0000000000..c8eca05dca --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java @@ -0,0 +1,1465 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; +import org.mockito.InOrder; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableRefCount.RefConnection; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.*; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableRefCountAltTest { + + @Test + public void testRefCountAsync() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Flowable<Long> r = Flowable.interval(0, 20, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer<Long>() { + @Override + public void accept(Long l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer<Long>() { + @Override + public void accept(Long l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + + for (;;) { + int a = nextCount.get(); + int b = receivedCount.get(); + if (a > 10 && a < 20 && a == b) { + break; + } + if (a >= 20) { + break; + } + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + } + // give time to emit + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); + + System.out.println("onNext: " + nextCount.get()); + + // should emit once for both subscribers + assertEquals(nextCount.get(), receivedCount.get()); + // only 1 subscribe + assertEquals(1, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronous() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Flowable<Integer> r = Flowable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); + + System.out.println("onNext Count: " + nextCount.get()); + + // it will emit twice because it is synchronous + assertEquals(nextCount.get(), receivedCount.get() * 2); + // it will subscribe twice because it is synchronous + assertEquals(2, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronousTake() { + final AtomicInteger nextCount = new AtomicInteger(); + Flowable<Integer> r = Flowable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + System.out.println("onNext --------> " + l); + nextCount.incrementAndGet(); + } + }) + .take(4) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + r.subscribe(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + System.out.println("onNext: " + nextCount.get()); + + assertEquals(4, receivedCount.get()); + assertEquals(4, receivedCount.get()); + } + + @Test + public void testRepeat() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger unsubscribeCount = new AtomicInteger(); + Flowable<Long> r = Flowable.interval(0, 1, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeCount.incrementAndGet(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeCount.incrementAndGet(); + } + }) + .publish().refCount(); + + for (int i = 0; i < 10; i++) { + TestSubscriber<Long> ts1 = new TestSubscriber<Long>(); + TestSubscriber<Long> ts2 = new TestSubscriber<Long>(); + r.subscribe(ts1); + r.subscribe(ts2); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + ts1.dispose(); + ts2.dispose(); + ts1.assertNoErrors(); + ts2.assertNoErrors(); + assertTrue(ts1.valueCount() > 0); + assertTrue(ts2.valueCount() > 0); + } + + assertEquals(10, subscribeCount.get()); + assertEquals(10, unsubscribeCount.get()); + } + + @Test + public void testConnectUnsubscribe() throws InterruptedException { + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final CountDownLatch subscribeLatch = new CountDownLatch(1); + + Flowable<Long> f = synchronousInterval() + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeLatch.countDown(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeLatch.countDown(); + } + }); + + TestSubscriber<Long> s = new TestSubscriber<Long>(); + f.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(s); + System.out.println("send unsubscribe"); + // wait until connected + subscribeLatch.await(); + // now unsubscribe + s.dispose(); + System.out.println("DONE sending unsubscribe ... now waiting"); + if (!unsubscribeLatch.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("Errors: " + s.errors()); + if (s.errors().size() > 0) { + s.errors().get(0).printStackTrace(); + } + fail("timed out waiting for unsubscribe"); + } + s.assertNoErrors(); + } + + @Test + public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedException { + for (int i = 0; i < 100; i++) { + testConnectUnsubscribeRaceCondition(); + } + } + + @Test + public void testConnectUnsubscribeRaceCondition() throws InterruptedException { + final AtomicInteger subUnsubCount = new AtomicInteger(); + Flowable<Long> f = synchronousInterval() + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + subUnsubCount.decrementAndGet(); + } + }) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* SUBSCRIBE received"); + subUnsubCount.incrementAndGet(); + } + }); + + TestSubscriber<Long> s = new TestSubscriber<Long>(); + + f.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); + System.out.println("send unsubscribe"); + // now immediately unsubscribe while subscribeOn is racing to subscribe + s.dispose(); + // this generally will mean it won't even subscribe as it is already unsubscribed by the time connect() gets scheduled + // give time to the counter to update + Thread.sleep(10); + // either we subscribed and then unsubscribed, or we didn't ever even subscribe + assertEquals(0, subUnsubCount.get()); + + System.out.println("DONE sending unsubscribe ... now waiting"); + System.out.println("Errors: " + s.errors()); + if (s.errors().size() > 0) { + s.errors().get(0).printStackTrace(); + } + s.assertNoErrors(); + } + + private Flowable<Long> synchronousInterval() { + return Flowable.unsafeCreate(new Publisher<Long>() { + @Override + public void subscribe(Subscriber<? super Long> subscriber) { + final AtomicBoolean cancel = new AtomicBoolean(); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + cancel.set(true); + } + + }); + for (;;) { + if (cancel.get()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + subscriber.onNext(1L); + } + } + }); + } + + @Test + public void onlyFirstShouldSubscribeAndLastUnsubscribe() { + final AtomicInteger subscriptionCount = new AtomicInteger(); + final AtomicInteger unsubscriptionCount = new AtomicInteger(); + Flowable<Integer> flowable = Flowable.unsafeCreate(new Publisher<Integer>() { + @Override + public void subscribe(Subscriber<? super Integer> subscriber) { + subscriptionCount.incrementAndGet(); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + unsubscriptionCount.incrementAndGet(); + } + }); + } + }); + Flowable<Integer> refCounted = flowable.publish().refCount(); + + Disposable first = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + Disposable second = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + first.dispose(); + assertEquals(0, unsubscriptionCount.get()); + + second.dispose(); + assertEquals(1, unsubscriptionCount.get()); + } + + @Test + public void testRefCount() { + TestScheduler s = new TestScheduler(); + Flowable<Long> interval = Flowable.interval(100, TimeUnit.MILLISECONDS, s).publish().refCount(); + + // subscribe list1 + final List<Long> list1 = new ArrayList<Long>(); + Disposable d1 = interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list1.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list1.size()); + assertEquals(0L, list1.get(0).longValue()); + assertEquals(1L, list1.get(1).longValue()); + + // subscribe list2 + final List<Long> list2 = new ArrayList<Long>(); + Disposable d2 = interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list2.add(t1); + } + }); + + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should have 5 items + assertEquals(5, list1.size()); + assertEquals(2L, list1.get(2).longValue()); + assertEquals(3L, list1.get(3).longValue()); + assertEquals(4L, list1.get(4).longValue()); + + // list 2 should only have 3 items + assertEquals(3, list2.size()); + assertEquals(2L, list2.get(0).longValue()); + assertEquals(3L, list2.get(1).longValue()); + assertEquals(4L, list2.get(2).longValue()); + + // unsubscribe list1 + d1.dispose(); + + // advance further + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should still have 5 items + assertEquals(5, list1.size()); + + // list 2 should have 6 items + assertEquals(6, list2.size()); + assertEquals(5L, list2.get(3).longValue()); + assertEquals(6L, list2.get(4).longValue()); + assertEquals(7L, list2.get(5).longValue()); + + // unsubscribe list2 + d2.dispose(); + + // advance further + s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + // subscribing a new one should start over because the source should have been unsubscribed + // subscribe list3 + final List<Long> list3 = new ArrayList<Long>(); + interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list3.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list3.size()); + assertEquals(0L, list3.get(0).longValue()); + assertEquals(1L, list3.get(1).longValue()); + + } + + @Test + public void testAlreadyUnsubscribedClient() { + Subscriber<Integer> done = CancelledSubscriber.INSTANCE; + + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + + Flowable<Integer> result = Flowable.just(1).publish().refCount(); + + result.subscribe(done); + + result.subscribe(subscriber); + + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void testAlreadyUnsubscribedInterleavesWithClient() { + ReplayProcessor<Integer> source = ReplayProcessor.create(); + + Subscriber<Integer> done = CancelledSubscriber.INSTANCE; + + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); + + Flowable<Integer> result = source.publish().refCount(); + + result.subscribe(subscriber); + + source.onNext(1); + + result.subscribe(done); + + source.onNext(2); + source.onComplete(); + + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void testConnectDisconnectConnectAndSubjectState() { + Flowable<Integer> f1 = Flowable.just(10); + Flowable<Integer> f2 = Flowable.just(20); + Flowable<Integer> combined = Flowable.combineLatest(f1, f2, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer t1, Integer t2) { + return t1 + t2; + } + }) + .publish().refCount(); + + TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + combined.subscribe(ts1); + combined.subscribe(ts2); + + ts1.assertTerminated(); + ts1.assertNoErrors(); + ts1.assertValue(30); + + ts2.assertTerminated(); + ts2.assertNoErrors(); + ts2.assertValue(30); + } + + @Test(timeout = 10000) + public void testUpstreamErrorAllowsRetry() throws InterruptedException { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicInteger intervalSubscribed = new AtomicInteger(); + Flowable<String> interval = + Flowable.interval(200, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); + } + } + ) + .flatMap(new Function<Long, Publisher<String>>() { + @Override + public Publisher<String> apply(Long t1) { + return Flowable.defer(new Callable<Publisher<String>>() { + @Override + public Publisher<String> call() { + return Flowable.<String>error(new TestException("Some exception")); + } + }); + } + }) + .onErrorResumeNext(new Function<Throwable, Publisher<String>>() { + @Override + public Publisher<String> apply(Throwable t1) { + return Flowable.error(t1); + } + }) + .publish() + .refCount(); + + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 1 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 1: " + t1); + } + }); + Thread.sleep(100); + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 2 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 2: " + t1); + } + }); + + Thread.sleep(1300); + + System.out.println(intervalSubscribed.get()); + assertEquals(6, intervalSubscribed.get()); + + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + private enum CancelledSubscriber implements FlowableSubscriber<Integer> { + INSTANCE; + + @Override public void onSubscribe(Subscription s) { + s.cancel(); + } + + @Override public void onNext(Integer o) { + } + + @Override public void onError(Throwable t) { + } + + @Override public void onComplete() { + } + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Flowable.just(1).publish().refCount()); + } + + @Test + public void noOpConnect() { + final int[] calls = { 0 }; + Flowable<Integer> f = new ConnectableFlowable<Integer>() { + @Override + public void connect(Consumer<? super Disposable> connection) { + calls[0]++; + } + + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + }.refCount(); + + f.test(); + f.test(); + + assertEquals(1, calls[0]); + } + + Flowable<Object> source; + + @Test + public void replayNoLeak() throws Exception { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }) + .replay(1) + .refCount(); + + source.subscribe(); + + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayNoLeak2() throws Exception { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Flowable.never()) + .replay(1) + .refCount(); + + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + static final class ExceptionData extends Exception { + private static final long serialVersionUID = -6763898015338136119L; + + public final Object data; + + ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + throw new ExceptionData(new byte[100 * 1000 * 1000]); + } + }) + .publish() + .refCount(); + + source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + + Thread.sleep(100); + System.gc(); + Thread.sleep(200); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void publishNoLeak2() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Flowable.never()) + .publish() + .refCount(); + + Disposable d1 = source.test(); + Disposable d2 = source.test(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayIsUnsubscribed() { + ConnectableFlowable<Integer> cf = Flowable.just(1) + .replay(); + + if (cf instanceof Disposable) { + assertTrue(((Disposable)cf).isDisposed()); + + Disposable connection = cf.connect(); + + assertFalse(((Disposable)cf).isDisposed()); + + connection.dispose(); + + assertTrue(((Disposable)cf).isDisposed()); + } + } + + static final class BadFlowableSubscribe extends ConnectableFlowable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + throw new TestException("subscribeActual"); + } + } + + static final class BadFlowableDispose extends ConnectableFlowable<Object> implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + static final class BadFlowableConnect extends ConnectableFlowable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + @Test + public void badSourceSubscribe() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe bo = new BadFlowableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badSourceDispose() { + BadFlowableDispose bf = new BadFlowableDispose(); + + try { + bf.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect bf = new BadFlowableConnect(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableSubscribe2 extends ConnectableFlowable<Object> { + + int count; + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + if (++count == 1) { + subscriber.onSubscribe(new BooleanSubscription()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe2 bf = new BadFlowableSubscribe2(); + + Flowable<Object> f = bf.refCount(); + f.test(); + try { + f.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableConnect2 extends ConnectableFlowable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect2 bf = new BadFlowableConnect2(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorProcessor<Integer> bp = BehaviorProcessor.createDefault(1); + + Flowable<Integer> f = bp + .replay(1) + .refCount(); + + f.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + f.switchMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) throws Exception { + return Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> emitter) throws Exception { + while (!emitter.isCancelled()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }, BackpressureStrategy.MISSING); + } + }) + .takeUntil(Flowable.timer(500, TimeUnit.MILLISECONDS)) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Flowable<Integer> source = Flowable.range(1, 5) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestSubscriber<Integer> ts1 = source.test(); + + ts1.assertEmpty(); + + TestSubscriber<Integer> ts2 = source.test(); + + ts1.assertResult(1, 2, 3, 4, 5); + ts2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + Thread.sleep(100); + + ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); + pp.onNext(5); + pp.onComplete(); + + ts1.requestMore(5) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + assertTrue(pp.hasSubscribers()); + + Thread.sleep(200); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void error() { + Flowable.<Integer>error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .publish() + .refCount(1); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertTrue(pp.hasSubscribers()); + + for (int i = 0; i < 3; i++) { + TestSubscriber<Integer> ts2 = source.test(); + ts1.cancel(); + ts1 = ts2; + } + + ts1.cancel(); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { + + final Flowable<Integer> source = Flowable.range(1, 5) + .replay() + .refCount(1) + ; + + final TestSubscriber<Integer> ts1 = source.test(0); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + ts2.requestMore(6) // FIXME RxJava replay() doesn't issue onComplete without request + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadFlowableDoubleOnX extends ConnectableFlowable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onComplete(); + subscriber.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + FlowableRefCount<Object> o = (FlowableRefCount<Object>)PublishProcessor.create() + .publish() + .refCount(); + + o.cancel(null); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + rc.set(null); + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Flowable<Integer> flowable = Flowable.just(1).replay(1).refCount(); + + TestSubscriber<Integer> ts1 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + TestSubscriber<Integer> ts2 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + ts1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + ts2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableFlowable<T> extends ConnectableFlowable<T> + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + // not relevant + } + + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + FlowableRefCount<Object> o = (FlowableRefCount<Object>)new TestConnectableFlowable<Object>().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorProcessor<Integer> processor = BehaviorProcessor.create(); + + Flowable<Integer> flowable = processor + .replay(1) + .refCount(); + + flowable.takeUntil(Flowable.just(1)).test(); + + processor.onNext(2); + + flowable.take(1).test().assertResult(2); + } + + @Test + public void publishRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Flowable<Integer> flowable = Flowable.just(1).publish().refCount(); + + TestSubscriber<Integer> subscriber1 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + TestSubscriber<Integer> subscriber2 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + subscriber1 + .withTag("subscriber1 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + + subscriber2 + .withTag("subscriber2 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + } + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplayProcessor<Integer> rp = ReplayProcessor.create(); + rp.onNext(1); + rp.onComplete(); + + Flowable<Integer> shared = rp.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java index a94152fc03..c032e61da5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java @@ -17,32 +17,59 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; import io.reactivex.flowables.ConnectableFlowable; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableRefCount.RefConnection; import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.processors.ReplayProcessor; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.*; import io.reactivex.schedulers.*; import io.reactivex.subscribers.TestSubscriber; public class FlowableRefCountTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableFlowableAssembly(new Function<ConnectableFlowable, ConnectableFlowable>() { + @Override + public ConnectableFlowable apply(ConnectableFlowable co) throws Exception { + if (co instanceof FlowablePublishAlt) { + FlowablePublishAlt fpa = (FlowablePublishAlt) co; + return FlowablePublish.create(Flowable.fromPublisher(fpa.source()), fpa.publishBufferSize()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableFlowableAssembly(null); + } + @Test public void testRefCountAsync() { final AtomicInteger subscribeCount = new AtomicInteger(); final AtomicInteger nextCount = new AtomicInteger(); - Flowable<Long> r = Flowable.interval(0, 5, TimeUnit.MILLISECONDS) + Flowable<Long> r = Flowable.interval(0, 20, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) { @@ -58,24 +85,39 @@ public void accept(Long l) { .publish().refCount(); final AtomicInteger receivedCount = new AtomicInteger(); - Disposable s1 = r.subscribe(new Consumer<Long>() { + Disposable d1 = r.subscribe(new Consumer<Long>() { @Override public void accept(Long l) { receivedCount.incrementAndGet(); } }); - Disposable s2 = r.subscribe(); + Disposable d2 = r.subscribe(); - // give time to emit try { - Thread.sleep(52); + Thread.sleep(10); } catch (InterruptedException e) { } + for (;;) { + int a = nextCount.get(); + int b = receivedCount.get(); + if (a > 10 && a < 20 && a == b) { + break; + } + if (a >= 20) { + break; + } + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + } + // give time to emit + // now unsubscribe - s2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other - s1.dispose(); + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); System.out.println("onNext: " + nextCount.get()); @@ -105,14 +147,14 @@ public void accept(Integer l) { .publish().refCount(); final AtomicInteger receivedCount = new AtomicInteger(); - Disposable s1 = r.subscribe(new Consumer<Integer>() { + Disposable d1 = r.subscribe(new Consumer<Integer>() { @Override public void accept(Integer l) { receivedCount.incrementAndGet(); } }); - Disposable s2 = r.subscribe(); + Disposable d2 = r.subscribe(); // give time to emit try { @@ -121,8 +163,8 @@ public void accept(Integer l) { } // now unsubscribe - s2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other - s1.dispose(); + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); System.out.println("onNext Count: " + nextCount.get()); @@ -209,7 +251,7 @@ public void testConnectUnsubscribe() throws InterruptedException { final CountDownLatch unsubscribeLatch = new CountDownLatch(1); final CountDownLatch subscribeLatch = new CountDownLatch(1); - Flowable<Long> o = synchronousInterval() + Flowable<Long> f = synchronousInterval() .doOnSubscribe(new Consumer<Subscription>() { @Override public void accept(Subscription s) { @@ -228,7 +270,7 @@ public void run() { }); TestSubscriber<Long> s = new TestSubscriber<Long>(); - o.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(s); + f.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(s); System.out.println("send unsubscribe"); // wait until connected subscribeLatch.await(); @@ -255,7 +297,7 @@ public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedExceptio @Test public void testConnectUnsubscribeRaceCondition() throws InterruptedException { final AtomicInteger subUnsubCount = new AtomicInteger(); - Flowable<Long> o = synchronousInterval() + Flowable<Long> f = synchronousInterval() .doOnCancel(new Action() { @Override public void run() { @@ -274,7 +316,7 @@ public void accept(Subscription s) { TestSubscriber<Long> s = new TestSubscriber<Long>(); - o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); + f.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); System.out.println("send unsubscribe"); // now immediately unsubscribe while subscribeOn is racing to subscribe s.dispose(); @@ -327,11 +369,11 @@ public void cancel() { public void onlyFirstShouldSubscribeAndLastUnsubscribe() { final AtomicInteger subscriptionCount = new AtomicInteger(); final AtomicInteger unsubscriptionCount = new AtomicInteger(); - Flowable<Integer> observable = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> flowable = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(Subscriber<? super Integer> observer) { + public void subscribe(Subscriber<? super Integer> subscriber) { subscriptionCount.incrementAndGet(); - observer.onSubscribe(new Subscription() { + subscriber.onSubscribe(new Subscription() { @Override public void request(long n) { @@ -344,7 +386,7 @@ public void cancel() { }); } }); - Flowable<Integer> refCounted = observable.publish().refCount(); + Flowable<Integer> refCounted = flowable.publish().refCount(); Disposable first = refCounted.subscribe(); assertEquals(1, subscriptionCount.get()); @@ -366,7 +408,7 @@ public void testRefCount() { // subscribe list1 final List<Long> list1 = new ArrayList<Long>(); - Disposable s1 = interval.subscribe(new Consumer<Long>() { + Disposable d1 = interval.subscribe(new Consumer<Long>() { @Override public void accept(Long t1) { list1.add(t1); @@ -381,7 +423,7 @@ public void accept(Long t1) { // subscribe list2 final List<Long> list2 = new ArrayList<Long>(); - Disposable s2 = interval.subscribe(new Consumer<Long>() { + Disposable d2 = interval.subscribe(new Consumer<Long>() { @Override public void accept(Long t1) { list2.add(t1); @@ -403,7 +445,7 @@ public void accept(Long t1) { assertEquals(4L, list2.get(2).longValue()); // unsubscribe list1 - s1.dispose(); + d1.dispose(); // advance further s.advanceTimeBy(300, TimeUnit.MILLISECONDS); @@ -418,7 +460,7 @@ public void accept(Long t1) { assertEquals(7L, list2.get(5).longValue()); // unsubscribe list2 - s2.dispose(); + d2.dispose(); // advance further s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); @@ -445,17 +487,17 @@ public void accept(Long t1) { public void testAlreadyUnsubscribedClient() { Subscriber<Integer> done = CancelledSubscriber.INSTANCE; - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); Flowable<Integer> result = Flowable.just(1).publish().refCount(); result.subscribe(done); - result.subscribe(o); + result.subscribe(subscriber); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -464,12 +506,12 @@ public void testAlreadyUnsubscribedInterleavesWithClient() { Subscriber<Integer> done = CancelledSubscriber.INSTANCE; - Subscriber<Integer> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); Flowable<Integer> result = source.publish().refCount(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); @@ -478,17 +520,17 @@ public void testAlreadyUnsubscribedInterleavesWithClient() { source.onNext(2); source.onComplete(); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testConnectDisconnectConnectAndSubjectState() { - Flowable<Integer> o1 = Flowable.just(10); - Flowable<Integer> o2 = Flowable.just(20); - Flowable<Integer> combined = Flowable.combineLatest(o1, o2, new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> f1 = Flowable.just(10); + Flowable<Integer> f2 = Flowable.just(20); + Flowable<Integer> combined = Flowable.combineLatest(f1, f2, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; @@ -513,70 +555,77 @@ public Integer apply(Integer t1, Integer t2) { @Test(timeout = 10000) public void testUpstreamErrorAllowsRetry() throws InterruptedException { - final AtomicInteger intervalSubscribed = new AtomicInteger(); - Flowable<String> interval = - Flowable.interval(200,TimeUnit.MILLISECONDS) - .doOnSubscribe(new Consumer<Subscription>() { - @Override - public void accept(Subscription s) { - System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); - } - } - ) - .flatMap(new Function<Long, Publisher<String>>() { - @Override - public Publisher<String> apply(Long t1) { - return Flowable.defer(new Callable<Publisher<String>>() { - @Override - public Publisher<String> call() { - return Flowable.<String>error(new Exception("Some exception")); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicInteger intervalSubscribed = new AtomicInteger(); + Flowable<String> interval = + Flowable.interval(200, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) { + System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); } - }); - } - }) - .onErrorResumeNext(new Function<Throwable, Publisher<String>>() { - @Override - public Publisher<String> apply(Throwable t1) { - return Flowable.error(t1); } - }) - .publish() - .refCount(); + ) + .flatMap(new Function<Long, Publisher<String>>() { + @Override + public Publisher<String> apply(Long t1) { + return Flowable.defer(new Callable<Publisher<String>>() { + @Override + public Publisher<String> call() { + return Flowable.<String>error(new TestException("Some exception")); + } + }); + } + }) + .onErrorResumeNext(new Function<Throwable, Publisher<String>>() { + @Override + public Publisher<String> apply(Throwable t1) { + return Flowable.error(t1); + } + }) + .publish() + .refCount(); + + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 1 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 1: " + t1); + } + }); + Thread.sleep(100); + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 2 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 2: " + t1); + } + }); - interval - .doOnError(new Consumer<Throwable>() { - @Override - public void accept(Throwable t1) { - System.out.println("Subscriber 1 onError: " + t1); - } - }) - .retry(5) - .subscribe(new Consumer<String>() { - @Override - public void accept(String t1) { - System.out.println("Subscriber 1: " + t1); - } - }); - Thread.sleep(100); - interval - .doOnError(new Consumer<Throwable>() { - @Override - public void accept(Throwable t1) { - System.out.println("Subscriber 2 onError: " + t1); - } - }) - .retry(5) - .subscribe(new Consumer<String>() { - @Override - public void accept(String t1) { - System.out.println("Subscriber 2: " + t1); - } - }); + Thread.sleep(1300); - Thread.sleep(1300); + System.out.println(intervalSubscribed.get()); + assertEquals(6, intervalSubscribed.get()); - System.out.println(intervalSubscribed.get()); - assertEquals(6, intervalSubscribed.get()); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } private enum CancelledSubscriber implements FlowableSubscriber<Integer> { @@ -604,20 +653,20 @@ public void disposed() { @Test public void noOpConnect() { final int[] calls = { 0 }; - Flowable<Integer> o = new ConnectableFlowable<Integer>() { + Flowable<Integer> f = new ConnectableFlowable<Integer>() { @Override public void connect(Consumer<? super Disposable> connection) { calls[0]++; } @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); } }.refCount(); - o.test(); - o.test(); + f.test(); + f.test(); assertEquals(1, calls[0]); } @@ -626,6 +675,7 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { @Test public void replayNoLeak() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -642,6 +692,7 @@ public Object call() throws Exception { source.subscribe(); + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -653,6 +704,7 @@ public Object call() throws Exception { @Test public void replayNoLeak2() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -667,15 +719,16 @@ public Object call() throws Exception { .replay(1) .refCount(); - Disposable s1 = source.subscribe(); - Disposable s2 = source.subscribe(); + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); - s1.dispose(); - s2.dispose(); + d1.dispose(); + d2.dispose(); - s1 = null; - s2 = null; + d1 = null; + d2 = null; + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -697,6 +750,7 @@ static final class ExceptionData extends Exception { @Test public void publishNoLeak() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -713,6 +767,7 @@ public Object call() throws Exception { source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -738,14 +793,14 @@ public Object call() throws Exception { .publish() .refCount(); - Disposable s1 = source.test(); - Disposable s2 = source.test(); + Disposable d1 = source.test(); + Disposable d2 = source.test(); - s1.dispose(); - s2.dispose(); + d1.dispose(); + d2.dispose(); - s1 = null; - s2 = null; + d1 = null; + d2 = null; System.gc(); Thread.sleep(100); @@ -758,17 +813,647 @@ public Object call() throws Exception { @Test public void replayIsUnsubscribed() { - ConnectableFlowable<Integer> co = Flowable.just(1) + ConnectableFlowable<Integer> cf = Flowable.just(1) .replay(); - assertTrue(((Disposable)co).isDisposed()); + if (cf instanceof Disposable) { + assertTrue(((Disposable)cf).isDisposed()); - Disposable s = co.connect(); + Disposable connection = cf.connect(); - assertFalse(((Disposable)co).isDisposed()); + assertFalse(((Disposable)cf).isDisposed()); - s.dispose(); + connection.dispose(); + + assertTrue(((Disposable)cf).isDisposed()); + } + } + + static final class BadFlowableSubscribe extends ConnectableFlowable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + throw new TestException("subscribeActual"); + } + } + + static final class BadFlowableDispose extends ConnectableFlowable<Object> implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + static final class BadFlowableConnect extends ConnectableFlowable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + @Test + public void badSourceSubscribe() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe bo = new BadFlowableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badSourceDispose() { + BadFlowableDispose bf = new BadFlowableDispose(); + + try { + bf.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect bf = new BadFlowableConnect(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableSubscribe2 extends ConnectableFlowable<Object> { + + int count; + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + if (++count == 1) { + subscriber.onSubscribe(new BooleanSubscription()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe2 bf = new BadFlowableSubscribe2(); + + Flowable<Object> f = bf.refCount(); + f.test(); + try { + f.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableConnect2 extends ConnectableFlowable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect2 bf = new BadFlowableConnect2(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorProcessor<Integer> bp = BehaviorProcessor.createDefault(1); + + Flowable<Integer> f = bp + .replay(1) + .refCount(); + + f.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + f.switchMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) throws Exception { + return Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> emitter) throws Exception { + while (!emitter.isCancelled()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }, BackpressureStrategy.MISSING); + } + }) + .takeUntil(Flowable.timer(500, TimeUnit.MILLISECONDS)) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); - assertTrue(((Disposable)co).isDisposed()); + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Flowable<Integer> source = Flowable.range(1, 5) + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestSubscriber<Integer> ts1 = source.test(); + + ts1.assertEmpty(); + + TestSubscriber<Integer> ts2 = source.test(); + + ts1.assertResult(1, 2, 3, 4, 5); + ts2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + Thread.sleep(100); + + ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); + pp.onNext(5); + pp.onComplete(); + + ts1.requestMore(5) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + assertTrue(pp.hasSubscribers()); + + Thread.sleep(200); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void error() { + Flowable.<Integer>error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + Flowable<Integer> source = pp + .publish() + .refCount(1); + + TestSubscriber<Integer> ts1 = source.test(0); + + assertTrue(pp.hasSubscribers()); + + for (int i = 0; i < 3; i++) { + TestSubscriber<Integer> ts2 = source.test(); + ts1.cancel(); + ts1 = ts2; + } + + ts1.cancel(); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { + + final Flowable<Integer> source = Flowable.range(1, 5) + .replay() + .refCount(1) + ; + + final TestSubscriber<Integer> ts1 = source.test(0); + + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + ts2.requestMore(6) // FIXME RxJava replay() doesn't issue onComplete without request + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadFlowableDoubleOnX extends ConnectableFlowable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onComplete(); + subscriber.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + FlowableRefCount<Object> o = (FlowableRefCount<Object>)PublishProcessor.create() + .publish() + .refCount(); + + o.cancel(null); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Flowable<Integer> flowable = Flowable.just(1).replay(1).refCount(); + + TestSubscriber<Integer> ts1 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + TestSubscriber<Integer> ts2 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + ts1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + ts2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableFlowable<T> extends ConnectableFlowable<T> + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + // not relevant + } + + @Override + protected void subscribeActual(Subscriber<? super T> subscriber) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + FlowableRefCount<Object> o = (FlowableRefCount<Object>)new TestConnectableFlowable<Object>().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorProcessor<Integer> processor = BehaviorProcessor.create(); + + Flowable<Integer> flowable = processor + .replay(1) + .refCount(); + + flowable.takeUntil(Flowable.just(1)).test(); + + processor.onNext(2); + + flowable.take(1).test().assertResult(2); + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplayProcessor<Integer> rp = ReplayProcessor.create(); + rp.onNext(1); + rp.onComplete(); + + Flowable<Integer> shared = rp.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java index 561da1a6d5..46d240b620 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -29,6 +28,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -37,19 +37,19 @@ public class FlowableRepeatTest { @Test(timeout = 2000) public void testRepetition() { - int NUM = 10; + int num = 10; final AtomicInteger count = new AtomicInteger(); int value = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> o) { - o.onNext(count.incrementAndGet()); - o.onComplete(); + public void subscribe(final Subscriber<? super Integer> subscriber) { + subscriber.onNext(count.incrementAndGet()); + subscriber.onComplete(); } }).repeat().subscribeOn(Schedulers.computation()) - .take(NUM).blockingLast(); + .take(num).blockingLast(); - assertEquals(NUM, value); + assertEquals(num, value); } @Test(timeout = 2000) @@ -100,58 +100,58 @@ public Integer apply(Integer t1) { @Test(timeout = 2000) public void testRepeatAndTake() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Flowable.just(1).repeat().take(10).subscribe(o); + Flowable.just(1).repeat().take(10).subscribe(subscriber); - verify(o, times(10)).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, times(10)).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test(timeout = 2000) public void testRepeatLimited() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Flowable.just(1).repeat(10).subscribe(o); + Flowable.just(1).repeat(10).subscribe(subscriber); - verify(o, times(10)).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, times(10)).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test(timeout = 2000) public void testRepeatError() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Flowable.error(new TestException()).repeat(10).subscribe(o); + Flowable.error(new TestException()).repeat(10).subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test(timeout = 2000) public void testRepeatZero() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Flowable.just(1).repeat(0).subscribe(o); + Flowable.just(1).repeat(0).subscribe(subscriber); - verify(o).onComplete(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test(timeout = 2000) public void testRepeatOne() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - Flowable.just(1).repeat(1).subscribe(o); + Flowable.just(1).repeat(1).subscribe(subscriber); - verify(o).onComplete(); - verify(o, times(1)).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, times(1)).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } /** Issue #2587. */ @@ -216,8 +216,8 @@ public void repeatWhenDefaultScheduler() { Flowable.just(1).repeatWhen((Function)new Function<Flowable, Flowable>() { @Override - public Flowable apply(Flowable o) { - return o.take(2); + public Flowable apply(Flowable f) { + return f.take(2); } }).subscribe(ts); @@ -235,8 +235,8 @@ public void repeatWhenTrampolineScheduler() { Flowable.just(1).subscribeOn(Schedulers.trampoline()) .repeatWhen((Function)new Function<Flowable, Flowable>() { @Override - public Flowable apply(Flowable o) { - return o.take(2); + public Flowable apply(Flowable f) { + return f.take(2); } }).subscribe(ts); @@ -259,6 +259,19 @@ public boolean getAsBoolean() throws Exception { .assertResult(1, 1, 1, 1, 1); } + @Test + public void repeatUntilCancel() { + Flowable.just(1) + .repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return true; + } + }) + .test(2L, true) + .assertEmpty(); + } + @Test public void repeatLongPredicateInvalid() { try { @@ -310,7 +323,7 @@ public boolean getAsBoolean() throws Exception { @Test public void shouldDisposeInnerObservable() { - final PublishProcessor<Object> subject = PublishProcessor.create(); + final PublishProcessor<Object> processor = PublishProcessor.create(); final Disposable disposable = Flowable.just("Leak") .repeatWhen(new Function<Flowable<Object>, Flowable<Object>>() { @Override @@ -318,16 +331,16 @@ public Flowable<Object> apply(Flowable<Object> completions) throws Exception { return completions.switchMap(new Function<Object, Flowable<Object>>() { @Override public Flowable<Object> apply(Object ignore) throws Exception { - return subject; + return processor; } }); } }) .subscribe(); - assertTrue(subject.hasSubscribers()); + assertTrue(processor.hasSubscribers()); disposable.dispose(); - assertFalse(subject.hasSubscribers()); + assertFalse(processor.hasSubscribers()); } @Test @@ -355,4 +368,129 @@ public Flowable<Object> apply(Flowable<Object> handler) throws Exception { .test() .assertResult(1, 2, 3, 1, 2, 3); } + + @Test + public void noCancelPreviousRepeat() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Integer> source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.repeat(5) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatUntil() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Integer> source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return times.getAndIncrement() == 4; + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Integer> source = Flowable.just(1).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatWhen(new Function<Flowable<Object>, Flowable<?>>() { + @Override + public Flowable<?> apply(Flowable<Object> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + final PublishProcessor<Integer> source = PublishProcessor.create(); + final PublishProcessor<Integer> signaller = PublishProcessor.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestSubscriber<Integer> ts = source.take(1) + .repeatWhen(new Function<Flowable<Object>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Object> v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.offer(1); + } + } + }; + + TestHelper.race(r1, r2); + + ts.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java index 58b5ffedd0..56daf0c5e5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java @@ -14,20 +14,20 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import io.reactivex.annotations.NonNull; import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.Scheduler.Worker; +import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; import io.reactivex.flowables.ConnectableFlowable; @@ -46,44 +46,44 @@ public class FlowableReplayTest { public void testBufferedReplay() { PublishProcessor<Integer> source = PublishProcessor.create(); - ConnectableFlowable<Integer> co = source.replay(3); - co.connect(); + ConnectableFlowable<Integer> cf = source.replay(3); + cf.connect(); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); source.onNext(1); source.onNext(2); source.onNext(3); - inOrder.verify(observer1, times(1)).onNext(1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(1); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); source.onNext(4); source.onComplete(); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } } @@ -91,14 +91,14 @@ public void testBufferedReplay() { public void testBufferedWindowReplay() { PublishProcessor<Integer> source = PublishProcessor.create(); TestScheduler scheduler = new TestScheduler(); - ConnectableFlowable<Integer> co = source.replay(3, 100, TimeUnit.MILLISECONDS, scheduler); - co.connect(); + ConnectableFlowable<Integer> cf = source.replay(3, 100, TimeUnit.MILLISECONDS, scheduler); + cf.connect(); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); source.onNext(1); scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS); @@ -107,33 +107,33 @@ public void testBufferedWindowReplay() { source.onNext(3); scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS); - inOrder.verify(observer1, times(1)).onNext(1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(1); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); source.onNext(4); source.onNext(5); scheduler.advanceTimeBy(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onNext(5); + inOrder.verify(subscriber1, times(1)).onNext(5); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onNext(5); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onNext(5); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } } @@ -143,14 +143,14 @@ public void testWindowedReplay() { PublishProcessor<Integer> source = PublishProcessor.create(); - ConnectableFlowable<Integer> co = source.replay(100, TimeUnit.MILLISECONDS, scheduler); - co.connect(); + ConnectableFlowable<Integer> cf = source.replay(100, TimeUnit.MILLISECONDS, scheduler); + cf.connect(); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); source.onNext(1); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); @@ -161,25 +161,25 @@ public void testWindowedReplay() { source.onComplete(); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); - inOrder.verify(observer1, times(1)).onNext(1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(1); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); - inOrder.verify(observer1, never()).onNext(3); + cf.subscribe(subscriber1); + inOrder.verify(subscriber1, never()).onNext(3); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } } @@ -208,37 +208,37 @@ public Flowable<Integer> apply(Flowable<Integer> t1) { Flowable<Integer> co = source.replay(selector); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); source.onNext(1); source.onNext(2); source.onNext(3); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onNext(6); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onNext(6); source.onNext(4); source.onComplete(); - inOrder.verify(observer1, times(1)).onNext(8); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onNext(8); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } @@ -270,37 +270,37 @@ public Flowable<Integer> apply(Flowable<Integer> t1) { Flowable<Integer> co = source.replay(selector, 3); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); source.onNext(1); source.onNext(2); source.onNext(3); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onNext(6); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onNext(6); source.onNext(4); source.onComplete(); - inOrder.verify(observer1, times(1)).onNext(8); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onNext(8); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } } @@ -332,10 +332,10 @@ public Flowable<Integer> apply(Flowable<Integer> t1) { Flowable<Integer> co = source.replay(selector, 100, TimeUnit.MILLISECONDS, scheduler); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); source.onNext(1); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); @@ -346,24 +346,24 @@ public Flowable<Integer> apply(Flowable<Integer> t1) { source.onComplete(); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onNext(6); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onNext(6); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + co.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onComplete(); + inOrder.verify(subscriber1, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onError(any(Throwable.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); } } @@ -371,45 +371,45 @@ public Flowable<Integer> apply(Flowable<Integer> t1) { public void testBufferedReplayError() { PublishProcessor<Integer> source = PublishProcessor.create(); - ConnectableFlowable<Integer> co = source.replay(3); - co.connect(); + ConnectableFlowable<Integer> cf = source.replay(3); + cf.connect(); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); source.onNext(1); source.onNext(2); source.onNext(3); - inOrder.verify(observer1, times(1)).onNext(1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(1); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); source.onNext(4); source.onError(new RuntimeException("Forced failure")); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onError(any(RuntimeException.class)); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onComplete(); + verify(subscriber1, never()).onComplete(); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); - inOrder.verify(observer1, times(1)).onNext(4); - inOrder.verify(observer1, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(4); + inOrder.verify(subscriber1, times(1)).onError(any(RuntimeException.class)); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onComplete(); + verify(subscriber1, never()).onComplete(); } } @@ -419,14 +419,14 @@ public void testWindowedReplayError() { PublishProcessor<Integer> source = PublishProcessor.create(); - ConnectableFlowable<Integer> co = source.replay(100, TimeUnit.MILLISECONDS, scheduler); - co.connect(); + ConnectableFlowable<Integer> cf = source.replay(100, TimeUnit.MILLISECONDS, scheduler); + cf.connect(); { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); + cf.subscribe(subscriber1); source.onNext(1); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); @@ -437,25 +437,25 @@ public void testWindowedReplayError() { source.onError(new RuntimeException("Forced failure")); scheduler.advanceTimeBy(60, TimeUnit.MILLISECONDS); - inOrder.verify(observer1, times(1)).onNext(1); - inOrder.verify(observer1, times(1)).onNext(2); - inOrder.verify(observer1, times(1)).onNext(3); + inOrder.verify(subscriber1, times(1)).onNext(1); + inOrder.verify(subscriber1, times(1)).onNext(2); + inOrder.verify(subscriber1, times(1)).onNext(3); - inOrder.verify(observer1, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber1, times(1)).onError(any(RuntimeException.class)); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onComplete(); + verify(subscriber1, never()).onComplete(); } { - Subscriber<Object> observer1 = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer1); + Subscriber<Object> subscriber1 = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber1); - co.subscribe(observer1); - inOrder.verify(observer1, never()).onNext(3); + cf.subscribe(subscriber1); + inOrder.verify(subscriber1, never()).onNext(3); - inOrder.verify(observer1, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber1, times(1)).onError(any(RuntimeException.class)); inOrder.verifyNoMoreInteractions(); - verify(observer1, never()).onComplete(); + verify(subscriber1, never()).onComplete(); } } @@ -474,8 +474,8 @@ public void accept(Integer v) { Flowable<Integer> result = source.replay( new Function<Flowable<Integer>, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Flowable<Integer> o) { - return o.take(2); + public Flowable<Integer> apply(Flowable<Integer> f) { + return f.take(2); } }); @@ -506,7 +506,6 @@ public void run() { } } - /* * test the basic expectation of OperatorMulticast via replay */ @@ -521,7 +520,7 @@ public void testIssue2191_UnsubscribeSource() throws Exception { Subscriber<Integer> spiedSubscriberAfterConnect = TestHelper.mockSubscriber(); // Flowable under test - Flowable<Integer> source = Flowable.just(1,2); + Flowable<Integer> source = Flowable.just(1, 2); ConnectableFlowable<Integer> replay = source .doOnNext(sourceNext) @@ -696,7 +695,6 @@ public static Worker workerSpy(final Disposable mockDisposable) { return spy(new InprocessWorker(mockDisposable)); } - private static class InprocessWorker extends Worker { private final Disposable mockDisposable; public boolean unsubscribed; @@ -807,17 +805,17 @@ public void accept(long t) { requested.addAndGet(t); } }); - ConnectableFlowable<Integer> co = source.replay(); + ConnectableFlowable<Integer> cf = source.replay(); TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(10L); TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(90L); - co.subscribe(ts1); - co.subscribe(ts2); + cf.subscribe(ts1); + cf.subscribe(ts2); ts2.request(10); - co.connect(); + cf.connect(); ts1.assertValueCount(10); ts1.assertNotTerminated(); @@ -838,17 +836,17 @@ public void accept(long t) { requested.addAndGet(t); } }); - ConnectableFlowable<Integer> co = source.replay(50); + ConnectableFlowable<Integer> cf = source.replay(50); TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(10L); TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(90L); - co.subscribe(ts1); - co.subscribe(ts2); + cf.subscribe(ts1); + cf.subscribe(ts2); ts2.request(10); - co.connect(); + cf.connect(); ts1.assertValueCount(10); ts1.assertNotTerminated(); @@ -876,6 +874,7 @@ public void testColdReplayNoBackpressure() { assertEquals((Integer)i, onNextEvents.get(i)); } } + @Test public void testColdReplayBackpressure() { Flowable<Integer> source = Flowable.range(0, 1000).replay().autoConnect(); @@ -900,19 +899,19 @@ public void testColdReplayBackpressure() { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); System.out.println("published observable being executed"); - observer.onNext("one"); - observer.onComplete(); + subscriber.onNext("one"); + subscriber.onComplete(); } }).start(); } @@ -922,7 +921,7 @@ public void run() { final CountDownLatch latch = new CountDownLatch(2); // subscribe once - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { @@ -933,7 +932,7 @@ public void accept(String v) { }); // subscribe again - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String v) { @@ -952,11 +951,11 @@ public void accept(String v) { @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); - Flowable<Integer> o = Flowable.just(1).doOnCancel(unsubscribe).cache(); - o.subscribe(); - o.subscribe(); - o.subscribe(); - verify(unsubscribe, times(1)).run(); + Flowable<Integer> f = Flowable.just(1).doOnCancel(unsubscribe).replay().autoConnect(); + f.subscribe(); + f.subscribe(); + f.subscribe(); + verify(unsubscribe, never()).run(); } @Test @@ -995,6 +994,7 @@ public void testAsync() { assertEquals(10000, ts2.values().size()); } } + @Test public void testAsyncComeAndGo() { Flowable<Long> source = Flowable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -1061,7 +1061,6 @@ public void testValuesAndThenError() { .concatWith(Flowable.<Integer>error(new TestException())) .replay().autoConnect(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.subscribe(ts); @@ -1167,7 +1166,6 @@ public void testSubscribersComeAndGoAtRequestBoundaries() { ts22.assertNoErrors(); ts22.dispose(); - TestSubscriber<Integer> ts3 = new TestSubscriber<Integer>(); source.subscribe(ts3); @@ -1223,7 +1221,6 @@ public void testSubscribersComeAndGoAtRequestBoundaries2() { ts22.assertNoErrors(); ts22.dispose(); - TestSubscriber<Integer> ts3 = new TestSubscriber<Integer>(); source.subscribe(ts3); @@ -1305,13 +1302,13 @@ public void source() { @Test public void connectRace() { - for (int i = 0; i < 500; i++) { - final ConnectableFlowable<Integer> co = Flowable.range(1, 3).replay(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 3).replay(); Runnable r = new Runnable() { @Override public void run() { - co.connect(); + cf.connect(); } }; @@ -1321,23 +1318,23 @@ public void run() { @Test public void subscribeRace() { - for (int i = 0; i < 500; i++) { - final ConnectableFlowable<Integer> co = Flowable.range(1, 3).replay(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 3).replay(); - final TestSubscriber<Integer> to1 = new TestSubscriber<Integer>(); - final TestSubscriber<Integer> to2 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { - co.subscribe(to1); + cf.subscribe(ts1); } }; Runnable r2 = new Runnable() { @Override public void run() { - co.subscribe(to2); + cf.subscribe(ts2); } }; @@ -1347,25 +1344,25 @@ public void run() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { - final ConnectableFlowable<Integer> co = Flowable.range(1, 3).replay(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 3).replay(); - final TestSubscriber<Integer> to1 = new TestSubscriber<Integer>(); - final TestSubscriber<Integer> to2 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); - co.subscribe(to1); + cf.subscribe(ts1); Runnable r1 = new Runnable() { @Override public void run() { - to1.cancel(); + ts1.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - co.subscribe(to2); + cf.subscribe(ts2); } }; @@ -1384,12 +1381,12 @@ public void cancelOnArrival() { @Test public void cancelOnArrival2() { - ConnectableFlowable<Integer> co = PublishProcessor.<Integer>create() + ConnectableFlowable<Integer> cf = PublishProcessor.<Integer>create() .replay(Integer.MAX_VALUE); - co.test(); + cf.test(); - co + cf .autoConnect() .test(Long.MAX_VALUE, true) .assertEmpty(); @@ -1397,11 +1394,11 @@ public void cancelOnArrival2() { @Test public void connectConsumerThrows() { - ConnectableFlowable<Integer> co = Flowable.range(1, 2) + ConnectableFlowable<Integer> cf = Flowable.range(1, 2) .replay(); try { - co.connect(new Consumer<Disposable>() { + cf.connect(new Consumer<Disposable>() { @Override public void accept(Disposable t) throws Exception { throw new TestException(); @@ -1412,11 +1409,11 @@ public void accept(Disposable t) throws Exception { // expected } - co.test().assertEmpty().cancel(); + cf.test().assertEmpty().cancel(); - co.connect(); + cf.connect(); - co.test().assertResult(1, 2); + cf.test().assertResult(1, 2); } @Test @@ -1425,12 +1422,12 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onNext(1); - observer.onError(new TestException("Second")); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onNext(1); + subscriber.onError(new TestException("Second")); + subscriber.onComplete(); } }.replay() .autoConnect() @@ -1445,17 +1442,17 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { @Test public void subscribeOnNextRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final ConnectableFlowable<Integer> co = ps.replay(); + final ConnectableFlowable<Integer> cf = pp.replay(); - final TestSubscriber<Integer> to1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { - co.subscribe(to1); + cf.subscribe(ts1); } }; @@ -1463,7 +1460,7 @@ public void run() { @Override public void run() { for (int j = 0; j < 1000; j++) { - ps.onNext(j); + pp.onNext(j); } } }; @@ -1474,19 +1471,19 @@ public void run() { @Test public void unsubscribeOnNextRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final ConnectableFlowable<Integer> co = ps.replay(); + final ConnectableFlowable<Integer> cf = pp.replay(); - final TestSubscriber<Integer> to1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); - co.subscribe(to1); + cf.subscribe(ts1); Runnable r1 = new Runnable() { @Override public void run() { - to1.dispose(); + ts1.dispose(); } }; @@ -1494,7 +1491,7 @@ public void run() { @Override public void run() { for (int j = 0; j < 1000; j++) { - ps.onNext(j); + pp.onNext(j); } } }; @@ -1505,24 +1502,24 @@ public void run() { @Test public void unsubscribeReplayRace() { - for (int i = 0; i < 500; i++) { - final ConnectableFlowable<Integer> co = Flowable.range(1, 1000).replay(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final ConnectableFlowable<Integer> cf = Flowable.range(1, 1000).replay(); - final TestSubscriber<Integer> to1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); - co.connect(); + cf.connect(); Runnable r1 = new Runnable() { @Override public void run() { - co.subscribe(to1); + cf.subscribe(ts1); } }; Runnable r2 = new Runnable() { @Override public void run() { - to1.dispose(); + ts1.dispose(); } }; @@ -1532,90 +1529,90 @@ public void run() { @Test public void reentrantOnNext() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t == 1) { - ps.onNext(2); - ps.onComplete(); + pp.onNext(2); + pp.onComplete(); } super.onNext(t); } }; - ps.replay().autoConnect().subscribe(to); + pp.replay().autoConnect().subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void reentrantOnNextBound() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t == 1) { - ps.onNext(2); - ps.onComplete(); + pp.onNext(2); + pp.onComplete(); } super.onNext(t); } }; - ps.replay(10).autoConnect().subscribe(to); + pp.replay(10).autoConnect().subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertResult(1, 2); + ts.assertResult(1, 2); } @Test public void reentrantOnNextCancel() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t == 1) { - ps.onNext(2); + pp.onNext(2); cancel(); } super.onNext(t); } }; - ps.replay().autoConnect().subscribe(to); + pp.replay().autoConnect().subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertValues(1); + ts.assertValues(1); } @Test public void reentrantOnNextCancelBounded() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t == 1) { - ps.onNext(2); + pp.onNext(2); cancel(); } super.onNext(t); } }; - ps.replay(10).autoConnect().subscribe(to); + pp.replay(10).autoConnect().subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertValues(1); + ts.assertValues(1); } @Test @@ -1755,4 +1752,291 @@ public Publisher<Object> apply(Flowable<Integer> v) throws Exception { .test() .assertFailureAndMessage(NullPointerException.class, "The selector returned a null Publisher"); } + + @Test + public void multicastSelectorCallableConnectableCrash() { + FlowableReplay.multicastSelector(new Callable<ConnectableFlowable<Object>>() { + @Override + public ConnectableFlowable<Object> call() throws Exception { + throw new TestException(); + } + }, Functions.<Flowable<Object>>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported( + Flowable.never() + .replay() + ); + } + + @Test + public void noHeadRetentionCompleteSize() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1); + + // the backpressure coordination would not accept items from source otherwise + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionErrorSize() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1); + + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionSize() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1); + + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + + assertNotNull(buf.get().value); + + buf.trimHead(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionCompleteTime() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1, TimeUnit.MINUTES, Schedulers.computation()); + + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionErrorTime() { + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1, TimeUnit.MINUTES, Schedulers.computation()); + + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionTime() { + TestScheduler sch = new TestScheduler(); + + PublishProcessor<Integer> source = PublishProcessor.create(); + + FlowableReplay<Integer> co = (FlowableReplay<Integer>)source + .replay(1, TimeUnit.MILLISECONDS, sch); + + co.test(); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + + sch.advanceTimeBy(2, TimeUnit.MILLISECONDS); + + source.onNext(2); + + assertNotNull(buf.get().value); + + buf.trimHead(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test(expected = TestException.class) + public void createBufferFactoryCrash() { + FlowableReplay.create(Flowable.just(1), new Callable<ReplayBuffer<Integer>>() { + @Override + public ReplayBuffer<Integer> call() throws Exception { + throw new TestException(); + } + }) + .connect(); + } + + @Test + public void createBufferFactoryCrashOnSubscribe() { + FlowableReplay.create(Flowable.just(1), new Callable<ReplayBuffer<Integer>>() { + @Override + public ReplayBuffer<Integer> call() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void currentDisposedWhenConnecting() { + FlowableReplay<Integer> fr = (FlowableReplay<Integer>)FlowableReplay.create(Flowable.<Integer>never(), 16); + fr.connect(); + + fr.current.get().dispose(); + assertTrue(fr.current.get().isDisposed()); + + fr.connect(); + + assertFalse(fr.current.get().isDisposed()); + } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + Flowable<byte[]> source = Flowable.range(1, 200) + .map(new Function<Integer, byte[]>() { + @Override + public byte[] apply(Integer v) throws Exception { + return new byte[1024 * 1024]; + } + }) + .replay(new Function<Flowable<byte[]>, Publisher<byte[]>>() { + @Override + public Publisher<byte[]> apply(final Flowable<byte[]> f) throws Exception { + return f.take(1) + .concatMap(new Function<byte[], Publisher<byte[]>>() { + @Override + public Publisher<byte[]> apply(byte[] v) throws Exception { + return f; + } + }); + } + }, 1) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer<byte[]>() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java index 25cb51af5a..d0c73867e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -30,7 +29,9 @@ import io.reactivex.exceptions.TestException; import io.reactivex.flowables.GroupedFlowable; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; @@ -111,29 +112,29 @@ public static class Tuple { @Test public void testRetryIndefinitely() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 20; - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); - origin.retry().subscribe(new TestSubscriber<String>(observer)); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + int numRetries = 20; + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); + origin.retry().subscribe(new TestSubscriber<String>(subscriber)); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSchedulingNotificationHandler() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 2; - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); - TestSubscriber<String> subscriber = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + int numRetries = 2; + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); origin.retryWhen(new Function<Flowable<? extends Throwable>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<? extends Throwable> t1) { @@ -151,26 +152,26 @@ public void accept(Throwable e) { e.printStackTrace(); } }) - .subscribe(subscriber); + .subscribe(ts); - subscriber.awaitTerminalEvent(); - InOrder inOrder = inOrder(observer); + ts.awaitTerminalEvent(); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(1 + numRetries)).onNext("beginningEveryTime"); // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testOnNextFromNotificationHandler() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - int NUM_RETRIES = 2; - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + int numRetries = 2; + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retryWhen(new Function<Flowable<? extends Throwable>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<? extends Throwable> t1) { @@ -182,58 +183,58 @@ public Integer apply(Throwable t1) { } }).startWith(0).cast(Object.class); } - }).subscribe(observer); + }).subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testOnCompletedFromNotificationHandler() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(1)); - TestSubscriber<String> subscriber = new TestSubscriber<String>(observer); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); origin.retryWhen(new Function<Flowable<? extends Throwable>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<? extends Throwable> t1) { return Flowable.empty(); } - }).subscribe(subscriber); + }).subscribe(ts); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onSubscribe((Subscription)notNull()); - inOrder.verify(observer, never()).onNext("beginningEveryTime"); - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, times(1)).onComplete(); - inOrder.verify(observer, never()).onError(any(Exception.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onSubscribe((Subscription)notNull()); + inOrder.verify(subscriber, never()).onNext("beginningEveryTime"); + inOrder.verify(subscriber, never()).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Exception.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testOnErrorFromNotificationHandler() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(2)); origin.retryWhen(new Function<Flowable<? extends Throwable>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<? extends Throwable> t1) { return Flowable.error(new RuntimeException()); } - }).subscribe(observer); - - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onSubscribe((Subscription)notNull()); - inOrder.verify(observer, never()).onNext("beginningEveryTime"); - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + }).subscribe(subscriber); + + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onSubscribe((Subscription)notNull()); + inOrder.verify(subscriber, never()).onNext("beginningEveryTime"); + inOrder.verify(subscriber, never()).onNext("onSuccessOnly"); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(RuntimeException.class)); inOrder.verifyNoMoreInteractions(); } @@ -270,71 +271,71 @@ public Object apply(Throwable o, Integer integer) { @Test public void testOriginFails() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(1)); - origin.subscribe(observer); + origin.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("beginningEveryTime"); - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, never()).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber, never()).onNext("onSuccessOnly"); + inOrder.verify(subscriber, never()).onComplete(); } @Test public void testRetryFail() { - int NUM_RETRIES = 1; - int NUM_FAILURES = 2; - Subscriber<String> observer = TestHelper.mockSubscriber(); - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry(NUM_RETRIES).subscribe(observer); + int numRetries = 1; + int numFailures = 2; + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry(numRetries).subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 2 attempts (first time fail, second time (1st retry) fail) - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(1 + numRetries)).onNext("beginningEveryTime"); // should only retry once, fail again and emit onError - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber, times(1)).onError(any(RuntimeException.class)); // no success - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext("onSuccessOnly"); + inOrder.verify(subscriber, never()).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testRetrySuccess() { - int NUM_FAILURES = 1; - Subscriber<String> observer = TestHelper.mockSubscriber(); - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry(3).subscribe(observer); + int numFailures = 1; + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry(3).subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testInfiniteRetry() { - int NUM_FAILURES = 20; - Subscriber<String> observer = TestHelper.mockSubscriber(); - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry().subscribe(observer); + int numFailures = 20; + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry().subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(subscriber, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -355,9 +356,9 @@ public void testRetrySubscribesAgainAfterError() throws Exception { Consumer<Integer> throwException = mock(Consumer.class); doThrow(new RuntimeException()).when(throwException).accept(Mockito.anyInt()); - // create a retrying observable based on a PublishProcessor - PublishProcessor<Integer> subject = PublishProcessor.create(); - subject + // create a retrying Flowable based on a PublishProcessor + PublishProcessor<Integer> processor = PublishProcessor.create(); + processor // record item .doOnNext(record) // throw a RuntimeException @@ -369,13 +370,13 @@ public void testRetrySubscribesAgainAfterError() throws Exception { inOrder.verifyNoMoreInteractions(); - subject.onNext(1); + processor.onNext(1); inOrder.verify(record).accept(1); - subject.onNext(2); + processor.onNext(2); inOrder.verify(record).accept(2); - subject.onNext(3); + processor.onNext(3); inOrder.verify(record).accept(3); inOrder.verifyNoMoreInteractions(); @@ -391,8 +392,8 @@ public static class FuncWithErrors implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> o) { - o.onSubscribe(new Subscription() { + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new Subscription() { final AtomicLong req = new AtomicLong(); // 0 = not set, 1 = fast path, 2 = backpressure final AtomicInteger path = new AtomicInteger(0); @@ -401,30 +402,30 @@ public void subscribe(final Subscriber<? super String> o) { @Override public void request(long n) { if (n == Long.MAX_VALUE && path.compareAndSet(0, 1)) { - o.onNext("beginningEveryTime"); + subscriber.onNext("beginningEveryTime"); int i = count.getAndIncrement(); if (i < numFailures) { - o.onError(new RuntimeException("forced failure: " + (i + 1))); + subscriber.onError(new RuntimeException("forced failure: " + (i + 1))); } else { - o.onNext("onSuccessOnly"); - o.onComplete(); + subscriber.onNext("onSuccessOnly"); + subscriber.onComplete(); } return; } if (n > 0 && req.getAndAdd(n) == 0 && (path.get() == 2 || path.compareAndSet(0, 2)) && !done) { int i = count.getAndIncrement(); if (i < numFailures) { - o.onNext("beginningEveryTime"); - o.onError(new RuntimeException("forced failure: " + (i + 1))); + subscriber.onNext("beginningEveryTime"); + subscriber.onError(new RuntimeException("forced failure: " + (i + 1))); done = true; } else { do { if (i == numFailures) { - o.onNext("beginningEveryTime"); + subscriber.onNext("beginningEveryTime"); } else if (i > numFailures) { - o.onNext("onSuccessOnly"); - o.onComplete(); + subscriber.onNext("onSuccessOnly"); + subscriber.onComplete(); done = true; break; } @@ -433,6 +434,7 @@ public void request(long n) { } } } + @Override public void cancel() { // TODO Auto-generated method stub @@ -444,17 +446,17 @@ public void cancel() { @Test public void testUnsubscribeFromRetry() { - PublishProcessor<Integer> subject = PublishProcessor.create(); + PublishProcessor<Integer> processor = PublishProcessor.create(); final AtomicInteger count = new AtomicInteger(0); - Disposable sub = subject.retry().subscribe(new Consumer<Integer>() { + Disposable sub = processor.retry().subscribe(new Consumer<Integer>() { @Override public void accept(Integer n) { count.incrementAndGet(); } }); - subject.onNext(1); + processor.onNext(1); sub.dispose(); - subject.onNext(2); + processor.onNext(2); assertEquals(1, count.get()); } @@ -491,7 +493,7 @@ public void cancel() { } @Test - public void testSourceObservableCallsUnsubscribe() throws InterruptedException { + public void testSourceFlowableCallsUnsubscribe() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber<String> ts = new TestSubscriber<String>(); @@ -522,7 +524,7 @@ public void subscribe(Subscriber<? super String> s) { } @Test - public void testSourceObservableRetry1() throws InterruptedException { + public void testSourceFlowableRetry1() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber<String> ts = new TestSubscriber<String>(); @@ -541,7 +543,7 @@ public void subscribe(Subscriber<? super String> s) { } @Test - public void testSourceObservableRetry0() throws InterruptedException { + public void testSourceFlowableRetry0() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); final TestSubscriber<String> ts = new TestSubscriber<String>(); @@ -565,12 +567,14 @@ static final class SlowFlowable implements Publisher<Long> { final AtomicInteger active = new AtomicInteger(0); final AtomicInteger maxActive = new AtomicInteger(0); final AtomicInteger nextBeforeFailure; + final String context; private final int emitDelay; - SlowFlowable(int emitDelay, int countNext) { + SlowFlowable(int emitDelay, int countNext, String context) { this.emitDelay = emitDelay; this.nextBeforeFailure = new AtomicInteger(countNext); + this.context = context; } @Override @@ -592,7 +596,7 @@ public void cancel() { efforts.getAndIncrement(); active.getAndIncrement(); maxActive.set(Math.max(active.get(), maxActive.get())); - final Thread thread = new Thread() { + final Thread thread = new Thread(context) { @Override public void run() { long nr = 0; @@ -602,7 +606,9 @@ public void run() { if (nextBeforeFailure.getAndDecrement() > 0) { subscriber.onNext(nr++); } else { + active.decrementAndGet(); subscriber.onError(new RuntimeException("expected-failed")); + break; } } } catch (InterruptedException t) { @@ -614,7 +620,7 @@ public void run() { } /** Observer for listener on seperate thread. */ - static final class AsyncObserver<T> extends DefaultSubscriber<T> { + static final class AsyncSubscriber<T> extends DefaultSubscriber<T> { protected CountDownLatch latch = new CountDownLatch(1); @@ -624,7 +630,7 @@ static final class AsyncObserver<T> extends DefaultSubscriber<T> { * Wrap existing Observer. * @param target the target subscriber */ - AsyncObserver(Subscriber<T> target) { + AsyncSubscriber(Subscriber<T> target) { this.target = target; } @@ -660,23 +666,22 @@ public void onNext(T v) { @Test(timeout = 10000) public void testUnsubscribeAfterError() { - @SuppressWarnings("unchecked") - DefaultSubscriber<Long> observer = mock(DefaultSubscriber.class); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); // Flowable that always fails after 100ms - SlowFlowable so = new SlowFlowable(100, 0); - Flowable<Long> o = Flowable.unsafeCreate(so).retry(5); + SlowFlowable so = new SlowFlowable(100, 0, "testUnsubscribeAfterError"); + Flowable<Long> f = Flowable.unsafeCreate(so).retry(5); - AsyncObserver<Long> async = new AsyncObserver<Long>(observer); + AsyncSubscriber<Long> async = new AsyncSubscriber<Long>(subscriber); - o.subscribe(async); + f.subscribe(async); async.await(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // Should fail once - inOrder.verify(observer, times(1)).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); assertEquals("Only 1 active subscription", 1, so.maxActive.get()); @@ -685,48 +690,47 @@ public void testUnsubscribeAfterError() { @Test//(timeout = 10000) public void testTimeoutWithRetry() { - @SuppressWarnings("unchecked") - DefaultSubscriber<Long> observer = mock(DefaultSubscriber.class); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); // Flowable that sends every 100ms (timeout fails instead) - SlowFlowable so = new SlowFlowable(100, 10); - Flowable<Long> o = Flowable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); + SlowFlowable sf = new SlowFlowable(100, 10, "testTimeoutWithRetry"); + Flowable<Long> f = Flowable.unsafeCreate(sf).timeout(80, TimeUnit.MILLISECONDS).retry(5); - AsyncObserver<Long> async = new AsyncObserver<Long>(observer); + AsyncSubscriber<Long> async = new AsyncSubscriber<Long>(subscriber); - o.subscribe(async); + f.subscribe(async); async.await(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // Should fail once - inOrder.verify(observer, times(1)).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); - assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); + assertEquals("Start 6 threads, retry 5 then fail on 6", 6, sf.efforts.get()); } @Test//(timeout = 15000) public void testRetryWithBackpressure() throws InterruptedException { final int NUM_LOOPS = 1; - for (int j = 0;j < NUM_LOOPS; j++) { - final int NUM_RETRIES = Flowable.bufferSize() * 2; + for (int j = 0; j < NUM_LOOPS; j++) { + final int numRetries = Flowable.bufferSize() * 2; for (int i = 0; i < 400; i++) { - Subscriber<String> observer = TestHelper.mockSubscriber(); - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); origin.retry().observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should have no errors - verify(observer, never()).onError(any(Throwable.class)); - // should show NUM_RETRIES attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + verify(subscriber, never()).onError(any(Throwable.class)); + // should show numRetries attempts + inOrder.verify(subscriber, times(numRetries + 1)).onNext("beginningEveryTime"); // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + inOrder.verify(subscriber, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } } @@ -735,7 +739,7 @@ public void testRetryWithBackpressure() throws InterruptedException { @Test//(timeout = 15000) public void testRetryWithBackpressureParallel() throws InterruptedException { final int NUM_LOOPS = 1; - final int NUM_RETRIES = Flowable.bufferSize() * 2; + final int numRetries = Flowable.bufferSize() * 2; int ncpu = Runtime.getRuntime().availableProcessors(); ExecutorService exec = Executors.newFixedThreadPool(Math.max(ncpu / 2, 2)); try { @@ -756,13 +760,13 @@ public void testRetryWithBackpressureParallel() throws InterruptedException { public void run() { final AtomicInteger nexts = new AtomicInteger(); try { - Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + Flowable<String> origin = Flowable.unsafeCreate(new FuncWithErrors(numRetries)); TestSubscriber<String> ts = new TestSubscriber<String>(); origin.retry() .observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); List<String> onNextEvents = new ArrayList<String>(ts.values()); - if (onNextEvents.size() != NUM_RETRIES + 2) { + if (onNextEvents.size() != numRetries + 2) { for (Throwable t : ts.errors()) { onNextEvents.add(t.toString()); } @@ -831,9 +835,10 @@ static <T> StringBuilder sequenceFrequency(Iterable<T> it) { return sb; } + @Test//(timeout = 3000) public void testIssue1900() throws InterruptedException { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final int NUM_MSG = 1034; final AtomicInteger count = new AtomicInteger(); @@ -852,40 +857,41 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function<GroupedFlowable<String,String>, Flowable<String>>() { + .flatMap(new Function<GroupedFlowable<String, String>, Flowable<String>>() { @Override public Flowable<String> apply(GroupedFlowable<String, String> t1) { return t1.take(1); } }) - .subscribe(new TestSubscriber<String>(observer)); + .subscribe(new TestSubscriber<String>(subscriber)); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(NUM_MSG)).onNext(any(java.lang.String.class)); + inOrder.verify(subscriber, times(NUM_MSG)).onNext(any(java.lang.String.class)); // // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success //inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } + @Test//(timeout = 3000) public void testIssue1900SourceNotSupportingBackpressure() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); final int NUM_MSG = 1034; final AtomicInteger count = new AtomicInteger(); Flowable<String> origin = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> o) { - o.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); for (int i = 0; i < NUM_MSG; i++) { - o.onNext("msg:" + count.incrementAndGet()); + subscriber.onNext("msg:" + count.incrementAndGet()); } - o.onComplete(); + subscriber.onComplete(); } }); @@ -896,23 +902,23 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function<GroupedFlowable<String,String>, Flowable<String>>() { + .flatMap(new Function<GroupedFlowable<String, String>, Flowable<String>>() { @Override public Flowable<String> apply(GroupedFlowable<String, String> t1) { return t1.take(1); } }) - .subscribe(new TestSubscriber<String>(observer)); + .subscribe(new TestSubscriber<String>(subscriber)); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // should show 3 attempts - inOrder.verify(observer, times(NUM_MSG)).onNext(any(java.lang.String.class)); + inOrder.verify(subscriber, times(NUM_MSG)).onNext(any(java.lang.String.class)); // // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); // should have a single success //inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); // should have a single successful onComplete - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -925,8 +931,8 @@ public void retryWhenDefaultScheduler() { .concatWith(Flowable.<Integer>error(new TestException())) .retryWhen((Function)new Function<Flowable, Flowable>() { @Override - public Flowable apply(Flowable o) { - return o.take(2); + public Flowable apply(Flowable f) { + return f.take(2); } }).subscribe(ts); @@ -946,8 +952,8 @@ public void retryWhenTrampolineScheduler() { .subscribeOn(Schedulers.trampoline()) .retryWhen((Function)new Function<Flowable, Flowable>() { @Override - public Flowable apply(Flowable o) { - return o.take(2); + public Flowable apply(Flowable f) { + return f.take(2); } }).subscribe(ts); @@ -999,10 +1005,9 @@ public boolean getAsBoolean() throws Exception { .assertResult(1, 1, 1, 1, 1); } - @Test - public void shouldDisposeInnerObservable() { - final PublishProcessor<Object> subject = PublishProcessor.create(); + public void shouldDisposeInnerFlowable() { + final PublishProcessor<Object> processor = PublishProcessor.create(); final Disposable disposable = Flowable.error(new RuntimeException("Leak")) .retryWhen(new Function<Flowable<Throwable>, Flowable<Object>>() { @Override @@ -1010,15 +1015,270 @@ public Flowable<Object> apply(Flowable<Throwable> errors) throws Exception { return errors.switchMap(new Function<Throwable, Flowable<Object>>() { @Override public Flowable<Object> apply(Throwable ignore) throws Exception { - return subject; + return processor; } }); } }) .subscribe(); - assertTrue(subject.hasSubscribers()); + assertTrue(processor.hasSubscribers()); disposable.dispose(); - assertFalse(subject.hasSubscribers()); + assertFalse(processor.hasSubscribers()); + } + + @Test + public void noCancelPreviousRetry() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.defer(new Callable<Flowable<Integer>>() { + @Override + public Flowable<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.defer(new Callable<Flowable<Integer>>() { + @Override + public Flowable<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5, Functions.alwaysTrue()) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.defer(new Callable<Flowable<Integer>>() { + @Override + public Flowable<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(new BiPredicate<Integer, Throwable>() { + @Override + public boolean test(Integer a, Throwable b) throws Exception { + return a < 5; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryUntil() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.defer(new Callable<Flowable<Integer>>() { + @Override + public Flowable<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.defer(new Callable<Flowable<Integer>>() { + @Override + public Flowable<Integer> call() throws Exception { + if (times.get() < 4) { + return Flowable.error(new TestException()); + } + return Flowable.just(1); + } + }).doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function<Flowable<Throwable>, Flowable<?>>() { + @Override + public Flowable<?> apply(Flowable<Throwable> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Flowable<Integer> source = Flowable.<Integer>error(new TestException()) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function<Flowable<Throwable>, Flowable<?>>() { + @Override + public Flowable<?> apply(Flowable<Throwable> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + final TestException error = new TestException(); + + try { + final PublishProcessor<Integer> source = PublishProcessor.create(); + final PublishProcessor<Integer> signaller = PublishProcessor.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestSubscriber<Integer> ts = source.take(1) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw error; + } + }) + .retryWhen(new Function<Flowable<Throwable>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Throwable> v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.offer(1); + } + } + }; + + TestHelper.race(r1, r2); + + ts.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java index 5664a973aa..c24e4f5f7d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -32,8 +31,8 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; public class FlowableRetryWithPredicateTest { @@ -59,17 +58,18 @@ public boolean test(Integer t1, Throwable t2) { public void testWithNothingToRetry() { Flowable<Integer> source = Flowable.range(0, 3); - Subscriber<Integer> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.retry(retryTwice).subscribe(o); + source.retry(retryTwice).subscribe(subscriber); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwice() { Flowable<Integer> source = Flowable.unsafeCreate(new Publisher<Integer>() { @@ -90,22 +90,22 @@ public void subscribe(Subscriber<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.retry(retryTwice).subscribe(o); + source.retry(retryTwice).subscribe(subscriber); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwiceAndGiveUp() { Flowable<Integer> source = Flowable.unsafeCreate(new Publisher<Integer>() { @@ -118,22 +118,22 @@ public void subscribe(Subscriber<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.retry(retryTwice).subscribe(o); + source.retry(retryTwice).subscribe(subscriber); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } + @Test public void testRetryOnSpecificException() { Flowable<Integer> source = Flowable.unsafeCreate(new Publisher<Integer>() { @@ -154,21 +154,21 @@ public void subscribe(Subscriber<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.retry(retryOnTestException).subscribe(o); + source.retry(retryOnTestException).subscribe(subscriber); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); @@ -191,60 +191,59 @@ public void subscribe(Subscriber<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class); - InOrder inOrder = inOrder(o); - - source.retry(retryOnTestException).subscribe(o); - - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(0); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onError(te); - verify(o, never()).onError(ioe); - verify(o, never()).onComplete(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); + + source.retry(retryOnTestException).subscribe(subscriber); + + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(0); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onError(te); + verify(subscriber, never()).onError(ioe); + verify(subscriber, never()).onComplete(); } @Test public void testUnsubscribeFromRetry() { - PublishProcessor<Integer> subject = PublishProcessor.create(); + PublishProcessor<Integer> processor = PublishProcessor.create(); final AtomicInteger count = new AtomicInteger(0); - Disposable sub = subject.retry(retryTwice).subscribe(new Consumer<Integer>() { + Disposable sub = processor.retry(retryTwice).subscribe(new Consumer<Integer>() { @Override public void accept(Integer n) { count.incrementAndGet(); } }); - subject.onNext(1); + processor.onNext(1); sub.dispose(); - subject.onNext(2); + processor.onNext(2); assertEquals(1, count.get()); } @Test(timeout = 10000) public void testUnsubscribeAfterError() { - Subscriber<Long> observer = TestHelper.mockSubscriber(); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); // Flowable that always fails after 100ms - FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 0); - Flowable<Long> o = Flowable + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 0, "testUnsubscribeAfterError"); + Flowable<Long> f = Flowable .unsafeCreate(so) .retry(retry5); - FlowableRetryTest.AsyncObserver<Long> async = new FlowableRetryTest.AsyncObserver<Long>(observer); + FlowableRetryTest.AsyncSubscriber<Long> async = new FlowableRetryTest.AsyncSubscriber<Long>(subscriber); - o.subscribe(async); + f.subscribe(async); async.await(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // Should fail once - inOrder.verify(observer, times(1)).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); assertEquals("Only 1 active subscription", 1, so.maxActive.get()); @@ -253,25 +252,25 @@ public void testUnsubscribeAfterError() { @Test(timeout = 10000) public void testTimeoutWithRetry() { - Subscriber<Long> observer = TestHelper.mockSubscriber(); + Subscriber<Long> subscriber = TestHelper.mockSubscriber(); // Flowable that sends every 100ms (timeout fails instead) - FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 10); - Flowable<Long> o = Flowable + FlowableRetryTest.SlowFlowable so = new FlowableRetryTest.SlowFlowable(100, 10, "testTimeoutWithRetry"); + Flowable<Long> f = Flowable .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) .retry(retry5); - FlowableRetryTest.AsyncObserver<Long> async = new FlowableRetryTest.AsyncObserver<Long>(observer); + FlowableRetryTest.AsyncSubscriber<Long> async = new FlowableRetryTest.AsyncSubscriber<Long>(subscriber); - o.subscribe(async); + f.subscribe(async); async.await(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); // Should fail once - inOrder.verify(observer, times(1)).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); assertEquals("Start 6 threads, retry 5 then fail on 6", 6, so.efforts.get()); } @@ -293,6 +292,7 @@ public Integer apply(Integer t1) { assertEquals(6, c.get()); assertEquals(Collections.singletonList(e), ts.errors()); } + @Test public void testJustAndRetry() throws Exception { final AtomicBoolean throwException = new AtomicBoolean(true); @@ -334,7 +334,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -358,7 +358,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -393,7 +393,7 @@ public boolean test(Integer t1, Throwable t2) { @Test public void predicateThrows() { - TestSubscriber<Object> to = Flowable.error(new TestException("Outer")) + TestSubscriber<Object> ts = Flowable.error(new TestException("Outer")) .retry(new Predicate<Throwable>() { @Override public boolean test(Throwable e) throws Exception { @@ -403,7 +403,7 @@ public boolean test(Throwable e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "Outer"); TestHelper.assertError(errors, 1, TestException.class, "Inner"); @@ -419,37 +419,41 @@ public void dontRetry() { @Test public void retryDisposeRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final TestException ex = new TestException(); + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps.retry(Functions.alwaysTrue()).test(); + final TestSubscriber<Integer> ts = pp.retry(Functions.alwaysTrue()).test(); - final TestException ex = new TestException(); - - Runnable r1 = new Runnable() { - @Override - public void run() { - ps.onError(ex); - } - }; + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; - Runnable r2 = new Runnable() { - @Override - public void run() { - to.cancel(); - } - }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertEmpty(); + ts.assertEmpty(); + } + } finally { + RxJavaPlugins.reset(); } } @Test public void bipredicateThrows() { - TestSubscriber<Object> to = Flowable.error(new TestException("Outer")) + TestSubscriber<Object> ts = Flowable.error(new TestException("Outer")) .retry(new BiPredicate<Integer, Throwable>() { @Override public boolean test(Integer n, Throwable e) throws Exception { @@ -459,7 +463,7 @@ public boolean test(Integer n, Throwable e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "Outer"); TestHelper.assertError(errors, 1, TestException.class, "Inner"); @@ -467,35 +471,40 @@ public boolean test(Integer n, Throwable e) throws Exception { @Test public void retryBiPredicateDisposeRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps.retry(new BiPredicate<Object, Object>() { - @Override - public boolean test(Object t1, Object t2) throws Exception { - return true; - } - }).test(); + final TestSubscriber<Integer> ts = pp.retry(new BiPredicate<Object, Object>() { + @Override + public boolean test(Object t1, Object t2) throws Exception { + return true; + } + }).test(); - final TestException ex = new TestException(); + final TestException ex = new TestException(); - Runnable r1 = new Runnable() { - @Override - public void run() { - ps.onError(ex); - } - }; + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; - Runnable r2 = new Runnable() { - @Override - public void run() { - to.cancel(); - } - }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - to.assertEmpty(); + ts.assertEmpty(); + } + } finally { + RxJavaPlugins.reset(); } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java index 743da3f284..1ea39f76ce 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.*; import io.reactivex.schedulers.*; @@ -32,78 +33,78 @@ public class FlowableSampleTest { private TestScheduler scheduler; private Scheduler.Worker innerScheduler; - private Subscriber<Long> observer; - private Subscriber<Object> observer2; + private Subscriber<Long> subscriber; + private Subscriber<Object> subscriber2; @Before // due to mocking public void before() { scheduler = new TestScheduler(); innerScheduler = scheduler.createWorker(); - observer = TestHelper.mockSubscriber(); - observer2 = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); + subscriber2 = TestHelper.mockSubscriber(); } @Test public void testSample() { Flowable<Long> source = Flowable.unsafeCreate(new Publisher<Long>() { @Override - public void subscribe(final Subscriber<? super Long> observer1) { - observer1.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super Long> subscriber1) { + subscriber1.onSubscribe(new BooleanSubscription()); innerScheduler.schedule(new Runnable() { @Override public void run() { - observer1.onNext(1L); + subscriber1.onNext(1L); } }, 1, TimeUnit.SECONDS); innerScheduler.schedule(new Runnable() { @Override public void run() { - observer1.onNext(2L); + subscriber1.onNext(2L); } }, 2, TimeUnit.SECONDS); innerScheduler.schedule(new Runnable() { @Override public void run() { - observer1.onComplete(); + subscriber1.onComplete(); } }, 3, TimeUnit.SECONDS); } }); Flowable<Long> sampled = source.sample(400L, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(800L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(any(Long.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any(Long.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(1200L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - verify(observer, never()).onNext(2L); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext(1L); + verify(subscriber, never()).onNext(2L); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(1600L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(1L); - verify(observer, never()).onNext(2L); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(1L); + verify(subscriber, never()).onNext(2L); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(2000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(1L); - inOrder.verify(observer, times(1)).onNext(2L); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(1L); + inOrder.verify(subscriber, times(1)).onNext(2L); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(3000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(1L); - inOrder.verify(observer, never()).onNext(2L); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(1L); + inOrder.verify(subscriber, never()).onNext(2L); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -112,7 +113,7 @@ public void sampleWithSamplerNormal() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); source.onNext(2); @@ -123,13 +124,13 @@ public void sampleWithSamplerNormal() { source.onComplete(); sampler.onNext(3); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, never()).onNext(1); - inOrder.verify(observer2, times(1)).onNext(2); - inOrder.verify(observer2, never()).onNext(3); - inOrder.verify(observer2, times(1)).onNext(4); - inOrder.verify(observer2, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, never()).onNext(1); + inOrder.verify(subscriber2, times(1)).onNext(2); + inOrder.verify(subscriber2, never()).onNext(3); + inOrder.verify(subscriber2, times(1)).onNext(4); + inOrder.verify(subscriber2, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -138,7 +139,7 @@ public void sampleWithSamplerNoDuplicates() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); source.onNext(2); @@ -153,13 +154,13 @@ public void sampleWithSamplerNoDuplicates() { source.onComplete(); sampler.onNext(3); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, never()).onNext(1); - inOrder.verify(observer2, times(1)).onNext(2); - inOrder.verify(observer2, never()).onNext(3); - inOrder.verify(observer2, times(1)).onNext(4); - inOrder.verify(observer2, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, never()).onNext(1); + inOrder.verify(subscriber2, times(1)).onNext(2); + inOrder.verify(subscriber2, never()).onNext(3); + inOrder.verify(subscriber2, times(1)).onNext(4); + inOrder.verify(subscriber2, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -168,7 +169,7 @@ public void sampleWithSamplerTerminatingEarly() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); source.onNext(2); @@ -178,12 +179,12 @@ public void sampleWithSamplerTerminatingEarly() { source.onNext(3); source.onNext(4); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, never()).onNext(1); - inOrder.verify(observer2, times(1)).onNext(2); - inOrder.verify(observer2, times(1)).onComplete(); - inOrder.verify(observer2, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, never()).onNext(1); + inOrder.verify(subscriber2, times(1)).onNext(2); + inOrder.verify(subscriber2, times(1)).onComplete(); + inOrder.verify(subscriber2, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -192,7 +193,7 @@ public void sampleWithSamplerEmitAndTerminate() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); source.onNext(2); @@ -202,13 +203,13 @@ public void sampleWithSamplerEmitAndTerminate() { sampler.onNext(2); sampler.onComplete(); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, never()).onNext(1); - inOrder.verify(observer2, times(1)).onNext(2); - inOrder.verify(observer2, never()).onNext(3); - inOrder.verify(observer2, times(1)).onComplete(); - inOrder.verify(observer2, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, never()).onNext(1); + inOrder.verify(subscriber2, times(1)).onNext(2); + inOrder.verify(subscriber2, never()).onNext(3); + inOrder.verify(subscriber2, times(1)).onComplete(); + inOrder.verify(subscriber2, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -217,15 +218,15 @@ public void sampleWithSamplerEmptySource() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onComplete(); sampler.onNext(1); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, times(1)).onComplete(); - verify(observer2, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, times(1)).onComplete(); + verify(subscriber2, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -234,16 +235,16 @@ public void sampleWithSamplerSourceThrows() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); source.onError(new RuntimeException("Forced failure!")); sampler.onNext(1); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, times(1)).onError(any(Throwable.class)); - verify(observer2, never()).onNext(any()); - verify(observer, never()).onComplete(); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, times(1)).onError(any(Throwable.class)); + verify(subscriber2, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -252,22 +253,22 @@ public void sampleWithSamplerThrows() { PublishProcessor<Integer> sampler = PublishProcessor.create(); Flowable<Integer> m = source.sample(sampler); - m.subscribe(observer2); + m.subscribe(subscriber2); source.onNext(1); sampler.onNext(1); sampler.onError(new RuntimeException("Forced failure!")); - InOrder inOrder = inOrder(observer2); - inOrder.verify(observer2, times(1)).onNext(1); - inOrder.verify(observer2, times(1)).onError(any(RuntimeException.class)); - verify(observer, never()).onComplete(); + InOrder inOrder = inOrder(subscriber2); + inOrder.verify(subscriber2, times(1)).onNext(1); + inOrder.verify(subscriber2, times(1)).onError(any(RuntimeException.class)); + verify(subscriber, never()).onComplete(); } @Test public void testSampleUnsubscribe() { final Subscription s = mock(Subscription.class); - Flowable<Integer> o = Flowable.unsafeCreate( + Flowable<Integer> f = Flowable.unsafeCreate( new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> subscriber) { @@ -275,7 +276,7 @@ public void subscribe(Subscriber<? super Integer> subscriber) { } } ); - o.throttleLast(1, TimeUnit.MILLISECONDS).subscribe().dispose(); + f.throttleLast(1, TimeUnit.MILLISECONDS).subscribe().dispose(); verify(s).cancel(); } @@ -305,11 +306,20 @@ public void backpressureOverflow() { @Test public void backpressureOverflowWithOtherPublisher() { - BehaviorProcessor.createDefault(1) - .sample(Flowable.timer(1, TimeUnit.MILLISECONDS)) - .test(0L) - .awaitDone(5, TimeUnit.SECONDS) - .assertFailure(MissingBackpressureException.class); + PublishProcessor<Integer> pp1 = PublishProcessor.create(); + PublishProcessor<Integer> pp2 = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp1 + .sample(pp2) + .test(0L); + + pp1.onNext(1); + pp2.onNext(2); + + ts.assertFailure(MissingBackpressureException.class); + + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); } @Test @@ -338,7 +348,7 @@ public void emitLastTimedCustomScheduler() { @Test public void emitLastTimedRunCompleteRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestScheduler scheduler = new TestScheduler(); final PublishProcessor<Integer> pp = PublishProcessor.create(); @@ -386,7 +396,7 @@ public void emitLastOtherEmpty() { @Test public void emitLastOtherRunCompleteRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final PublishProcessor<Integer> sampler = PublishProcessor.create(); @@ -417,7 +427,7 @@ public void run() { @Test public void emitLastOtherCompleteCompleteRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final PublishProcessor<Integer> sampler = PublishProcessor.create(); @@ -444,4 +454,29 @@ public void run() { ts.assertResult(1); } } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.sample(1, TimeUnit.SECONDS); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.sample(PublishProcessor.create()); + } + }); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(PublishProcessor.create() + .sample(PublishProcessor.create())); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java index 0f0a4e4486..14f98a6c76 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScalarXMapTest.java @@ -24,7 +24,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.*; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableScalarXMapTest { @@ -179,9 +178,9 @@ public Publisher<Integer> apply(Integer v) throws Exception { @Test public void scalarDisposableStateCheck() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - ScalarSubscription<Integer> sd = new ScalarSubscription<Integer>(to, 1); - to.onSubscribe(sd); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ScalarSubscription<Integer> sd = new ScalarSubscription<Integer>(ts, 1); + ts.onSubscribe(sd); assertFalse(sd.isCancelled()); @@ -193,7 +192,7 @@ public void scalarDisposableStateCheck() { assertTrue(sd.isEmpty()); - to.assertResult(1); + ts.assertResult(1); try { sd.offer(1); @@ -212,10 +211,10 @@ public void scalarDisposableStateCheck() { @Test public void scalarDisposableRunDisposeRace() { - for (int i = 0; i < 500; i++) { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - final ScalarSubscription<Integer> sd = new ScalarSubscription<Integer>(to, 1); - to.onSubscribe(sd); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + final ScalarSubscription<Integer> sd = new ScalarSubscription<Integer>(ts, 1); + ts.onSubscribe(sd); Runnable r1 = new Runnable() { @Override @@ -231,7 +230,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java index a01495295e..1b2b9ac4ca 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java @@ -24,7 +24,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.flowable.*; import io.reactivex.flowable.FlowableEventStream.Event; @@ -37,11 +36,11 @@ public class FlowableScanTest { @Test public void testScanIntegersWithInitialValue() { - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<Integer> observable = Flowable.just(1, 2, 3); + Flowable<Integer> flowable = Flowable.just(1, 2, 3); - Flowable<String> m = observable.scan("", new BiFunction<String, Integer, String>() { + Flowable<String> m = flowable.scan("", new BiFunction<String, Integer, String>() { @Override public String apply(String s, Integer n) { @@ -49,25 +48,25 @@ public String apply(String s, Integer n) { } }); - m.subscribe(observer); + m.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onNext(""); - verify(observer, times(1)).onNext("1"); - verify(observer, times(1)).onNext("12"); - verify(observer, times(1)).onNext("123"); - verify(observer, times(4)).onNext(anyString()); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(""); + verify(subscriber, times(1)).onNext("1"); + verify(subscriber, times(1)).onNext("12"); + verify(subscriber, times(1)).onNext("123"); + verify(subscriber, times(4)).onNext(anyString()); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testScanIntegersWithoutInitialValue() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - Flowable<Integer> observable = Flowable.just(1, 2, 3); + Flowable<Integer> flowable = Flowable.just(1, 2, 3); - Flowable<Integer> m = observable.scan(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> m = flowable.scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { @@ -75,25 +74,25 @@ public Integer apply(Integer t1, Integer t2) { } }); - m.subscribe(observer); + m.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onNext(0); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onNext(6); - verify(observer, times(3)).onNext(anyInt()); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(0); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, times(1)).onNext(6); + verify(subscriber, times(3)).onNext(anyInt()); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testScanIntegersWithoutInitialValueAndOnlyOneValue() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - Flowable<Integer> observable = Flowable.just(1); + Flowable<Integer> flowable = Flowable.just(1); - Flowable<Integer> m = observable.scan(new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> m = flowable.scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { @@ -101,14 +100,14 @@ public Integer apply(Integer t1, Integer t2) { } }); - m.subscribe(observer); + m.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onNext(0); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(anyInt()); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(0); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(anyInt()); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -283,7 +282,7 @@ public void accept(List<Integer> list, Integer t2) { */ @Test public void testSeedFactoryFlowable() { - Flowable<List<Integer>> o = Flowable.range(1, 10) + Flowable<List<Integer>> f = Flowable.range(1, 10) .collect(new Callable<List<Integer>>() { @Override @@ -300,13 +299,13 @@ public void accept(List<Integer> list, Integer t2) { }).toFlowable().takeLast(1); - assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingSingle()); - assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingSingle()); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), f.blockingSingle()); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), f.blockingSingle()); } @Test public void testScanWithRequestOne() { - Flowable<Integer> o = Flowable.just(1, 2).scan(0, new BiFunction<Integer, Integer, Integer>() { + Flowable<Integer> f = Flowable.just(1, 2).scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { @@ -315,7 +314,7 @@ public Integer apply(Integer t1, Integer t2) { }).take(1); TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); - o.subscribe(subscriber); + f.subscribe(subscriber); subscriber.assertValue(0); subscriber.assertTerminated(); subscriber.assertNoErrors(); @@ -324,7 +323,7 @@ public Integer apply(Integer t1, Integer t2) { @Test public void testScanShouldNotRequestZero() { final AtomicReference<Subscription> producer = new AtomicReference<Subscription>(); - Flowable<Integer> o = Flowable.unsafeCreate(new Publisher<Integer>() { + Flowable<Integer> f = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(final Subscriber<? super Integer> subscriber) { Subscription p = spy(new Subscription() { @@ -356,7 +355,7 @@ public Integer apply(Integer t1, Integer t2) { }); - o.subscribe(new TestSubscriber<Integer>(1L) { + f.subscribe(new TestSubscriber<Integer>(1L) { @Override public void onNext(Integer integer) { @@ -427,8 +426,8 @@ public Integer apply(Integer a, Integer b) throws Exception { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.scan(new BiFunction<Object, Object, Object>() { + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.scan(new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; @@ -439,8 +438,8 @@ public Object apply(Object a, Object b) throws Exception { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.scan(0, new BiFunction<Object, Object, Object>() { + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.scan(0, new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; @@ -553,7 +552,7 @@ public Integer apply(Integer n1, Integer n2) throws Exception { @Test public void testScanWithSeedCompletesNormally() { - Flowable.just(1,2,3).scan(0, SUM) + Flowable.just(1, 2, 3).scan(0, SUM) .test() .assertValues(0, 1, 3, 6) .assertComplete(); @@ -562,7 +561,7 @@ public void testScanWithSeedCompletesNormally() { @Test public void testScanWithSeedWhenScanSeedProviderThrows() { final RuntimeException e = new RuntimeException(); - Flowable.just(1,2,3).scanWith(throwingCallable(e), + Flowable.just(1, 2, 3).scanWith(throwingCallable(e), SUM) .test() .assertError(e) @@ -631,7 +630,7 @@ public Integer apply(Integer n1, Integer n2) throws Exception { assertEquals(1, count.get()); } - private static BiFunction<Integer,Integer, Integer> throwingBiFunction(final RuntimeException e) { + private static BiFunction<Integer, Integer, Integer> throwingBiFunction(final RuntimeException e) { return new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer n1, Integer n2) throws Exception { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java index 7036ff3d9e..b40a40bb56 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import java.util.List; @@ -37,98 +36,98 @@ public class FlowableSequenceEqualTest { @Test public void test1Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three")).toFlowable(); - verifyResult(observable, true); + verifyResult(flowable, true); } @Test public void test2Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three", "four")).toFlowable(); - verifyResult(observable, false); + verifyResult(flowable, false); } @Test public void test3Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one", "two", "three", "four"), Flowable.just("one", "two", "three")).toFlowable(); - verifyResult(observable, false); + verifyResult(flowable, false); } @Test public void testWithError1Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException())), Flowable.just("one", "two", "three")).toFlowable(); - verifyError(observable); + verifyError(flowable); } @Test public void testWithError2Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException()))).toFlowable(); - verifyError(observable); + verifyError(flowable); } @Test public void testWithError3Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException())), Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException()))).toFlowable(); - verifyError(observable); + verifyError(flowable); } @Test public void testWithEmpty1Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.<String> empty(), Flowable.just("one", "two", "three")).toFlowable(); - verifyResult(observable, false); + verifyResult(flowable, false); } @Test public void testWithEmpty2Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.<String> empty()).toFlowable(); - verifyResult(observable, false); + verifyResult(flowable, false); } @Test public void testWithEmpty3Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.<String> empty(), Flowable.<String> empty()).toFlowable(); - verifyResult(observable, true); + verifyResult(flowable, true); } @Test @Ignore("Null values not allowed") public void testWithNull1Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just("one")).toFlowable(); - verifyResult(observable, false); + verifyResult(flowable, false); } @Test @Ignore("Null values not allowed") public void testWithNull2Flowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just((String) null)).toFlowable(); - verifyResult(observable, true); + verifyResult(flowable, true); } @Test public void testWithEqualityErrorFlowable() { - Flowable<Boolean> observable = Flowable.sequenceEqual( + Flowable<Boolean> flowable = Flowable.sequenceEqual( Flowable.just("one"), Flowable.just("one"), new BiPredicate<String, String>() { @Override @@ -136,103 +135,103 @@ public boolean test(String t1, String t2) { throw new TestException(); } }).toFlowable(); - verifyError(observable); + verifyError(flowable); } @Test public void test1() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three")); - verifyResult(observable, true); + verifyResult(single, true); } @Test public void test2() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.just("one", "two", "three", "four")); - verifyResult(observable, false); + verifyResult(single, false); } @Test public void test3() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one", "two", "three", "four"), Flowable.just("one", "two", "three")); - verifyResult(observable, false); + verifyResult(single, false); } @Test public void testWithError1() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException())), Flowable.just("one", "two", "three")); - verifyError(observable); + verifyError(single); } @Test public void testWithError2() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException()))); - verifyError(observable); + verifyError(single); } @Test public void testWithError3() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException())), Flowable.concat(Flowable.just("one"), Flowable.<String> error(new TestException()))); - verifyError(observable); + verifyError(single); } @Test public void testWithEmpty1() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.<String> empty(), Flowable.just("one", "two", "three")); - verifyResult(observable, false); + verifyResult(single, false); } @Test public void testWithEmpty2() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one", "two", "three"), Flowable.<String> empty()); - verifyResult(observable, false); + verifyResult(single, false); } @Test public void testWithEmpty3() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.<String> empty(), Flowable.<String> empty()); - verifyResult(observable, true); + verifyResult(single, true); } @Test @Ignore("Null values not allowed") public void testWithNull1() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just("one")); - verifyResult(observable, false); + verifyResult(single, false); } @Test @Ignore("Null values not allowed") public void testWithNull2() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just((String) null), Flowable.just((String) null)); - verifyResult(observable, true); + verifyResult(single, true); } @Test public void testWithEqualityError() { - Single<Boolean> observable = Flowable.sequenceEqual( + Single<Boolean> single = Flowable.sequenceEqual( Flowable.just("one"), Flowable.just("one"), new BiPredicate<String, String>() { @Override @@ -240,42 +239,42 @@ public boolean test(String t1, String t2) { throw new TestException(); } }); - verifyError(observable); + verifyError(single); } - private void verifyResult(Flowable<Boolean> observable, boolean result) { - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); + private void verifyResult(Flowable<Boolean> flowable, boolean result) { + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); - observable.subscribe(observer); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(result); - inOrder.verify(observer).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(result); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } - private void verifyResult(Single<Boolean> observable, boolean result) { + private void verifyResult(Single<Boolean> single, boolean result) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(result); inOrder.verifyNoMoreInteractions(); } - private void verifyError(Flowable<Boolean> observable) { - Subscriber<Boolean> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + private void verifyError(Flowable<Boolean> flowable) { + Subscriber<Boolean> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(isA(TestException.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError(isA(TestException.class)); inOrder.verifyNoMoreInteractions(); } - private void verifyError(Single<Boolean> observable) { + private void verifyError(Single<Boolean> single) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError(isA(TestException.class)); @@ -312,10 +311,10 @@ public void simpleInequalObservable() { @Test public void onNextCancelRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestObserver<Boolean> to = Flowable.sequenceEqual(Flowable.never(), ps).test(); + final TestObserver<Boolean> to = Flowable.sequenceEqual(Flowable.never(), pp).test(); Runnable r1 = new Runnable() { @Override @@ -327,7 +326,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ps.onNext(1); + pp.onNext(1); } }; @@ -339,28 +338,28 @@ public void run() { @Test public void onNextCancelRaceObservable() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Boolean> to = Flowable.sequenceEqual(Flowable.never(), ps).toFlowable().test(); + final TestSubscriber<Boolean> ts = Flowable.sequenceEqual(Flowable.never(), pp).toFlowable().test(); Runnable r1 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps.onNext(1); + pp.onNext(1); } }; TestHelper.race(r1, r2); - to.assertEmpty(); + ts.assertEmpty(); } } @@ -414,7 +413,7 @@ protected void subscribeActual(Subscriber<? super Object> s) { } }; - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Boolean> ts = new TestSubscriber<Boolean>(); final PublishProcessor<Integer> pp = PublishProcessor.create(); @@ -483,7 +482,6 @@ protected void subscribeActual(Subscriber<? super Object> s) { } } - @Test public void longSequenceEquals() { Flowable<Integer> source = Flowable.range(1, Flowable.bufferSize() * 4).subscribeOn(Schedulers.computation()); @@ -518,15 +516,15 @@ protected void subscribeActual(Subscriber<? super Object> s) { } }; - for (int i = 0; i < 500; i++) { - final TestObserver<Boolean> ts = new TestObserver<Boolean>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestObserver<Boolean> to = new TestObserver<Boolean>(); final PublishProcessor<Integer> pp = PublishProcessor.create(); boolean swap = (i & 1) == 0; Flowable.sequenceEqual(swap ? pp : neverNever, swap ? neverNever : pp) - .subscribe(ts); + .subscribe(to); Runnable r1 = new Runnable() { @Override @@ -538,13 +536,13 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; TestHelper.race(r1, r2); - ts.assertEmpty(); + to.assertEmpty(); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSerializeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSerializeTest.java index 8fe8431271..6094955e83 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSerializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSerializeTest.java @@ -28,11 +28,11 @@ public class FlowableSerializeTest { - Subscriber<String> observer; + Subscriber<String> subscriber; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } @Test @@ -40,14 +40,14 @@ public void testSingleThreadedBasic() { TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable("one", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(onSubscribe); - w.serialize().subscribe(observer); + w.serialize().subscribe(subscriber); onSubscribe.waitToFinish(); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); // non-deterministic because unsubscribe happens after 'waitToFinish' releases // so commenting out for now as this is not a critical thing to test here // verify(s, times(1)).unsubscribe(); @@ -144,18 +144,18 @@ public void testMultiThreadedWithNPEinMiddle() { */ static class OnNextThread implements Runnable { - private final DefaultSubscriber<String> observer; + private final DefaultSubscriber<String> subscriber; private final int numStringsToSend; - OnNextThread(DefaultSubscriber<String> observer, int numStringsToSend) { - this.observer = observer; + OnNextThread(DefaultSubscriber<String> subscriber, int numStringsToSend) { + this.subscriber = subscriber; this.numStringsToSend = numStringsToSend; } @Override public void run() { for (int i = 0; i < numStringsToSend; i++) { - observer.onNext("aString"); + subscriber.onNext("aString"); } } } @@ -165,12 +165,12 @@ public void run() { */ static class CompletionThread implements Runnable { - private final DefaultSubscriber<String> observer; + private final DefaultSubscriber<String> subscriber; private final TestConcurrencyobserverEvent event; private final Future<?>[] waitOnThese; - CompletionThread(DefaultSubscriber<String> observer, TestConcurrencyobserverEvent event, Future<?>... waitOnThese) { - this.observer = observer; + CompletionThread(DefaultSubscriber<String> subscriber, TestConcurrencyobserverEvent event, Future<?>... waitOnThese) { + this.subscriber = subscriber; this.event = event; this.waitOnThese = waitOnThese; } @@ -190,9 +190,9 @@ public void run() { /* send the event */ if (event == TestConcurrencyobserverEvent.onError) { - observer.onError(new RuntimeException("mocked exception")); + subscriber.onError(new RuntimeException("mocked exception")); } else if (event == TestConcurrencyobserverEvent.onComplete) { - observer.onComplete(); + subscriber.onComplete(); } else { throw new IllegalArgumentException("Expecting either onError or onComplete"); @@ -218,8 +218,8 @@ private static class TestSingleThreadedObservable implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("TestSingleThreadedObservable subscribed to ..."); t = new Thread(new Runnable() { @@ -229,9 +229,9 @@ public void run() { System.out.println("running TestSingleThreadedObservable thread"); for (String s : values) { System.out.println("TestSingleThreadedObservable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { throw new RuntimeException(e); } @@ -269,8 +269,8 @@ private static class TestMultiThreadedObservable implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("TestMultiThreadedObservable subscribed to ..."); final NullPointerException npe = new NullPointerException(); t = new Thread(new Runnable() { @@ -299,7 +299,7 @@ public void run() { } System.out.println("TestMultiThreadedObservable onNext: " + s); } - observer.onNext(s); + subscriber.onNext(s); // capture 'maxThreads' int concurrentThreads = threadsRunning.get(); int maxThreads = maxConcurrentThreads.get(); @@ -307,7 +307,7 @@ public void run() { maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); } } catch (Throwable e) { - observer.onError(e); + subscriber.onError(e); } finally { threadsRunning.decrementAndGet(); } @@ -327,7 +327,7 @@ public void run() { } catch (InterruptedException e) { throw new RuntimeException(e); } - observer.onComplete(); + subscriber.onComplete(); } }); System.out.println("starting TestMultiThreadedObservable thread"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java index 67c5c83881..71085466cb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -25,49 +24,49 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.functions.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subscribers.DefaultSubscriber; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subscribers.*; public class FlowableSingleTest { @Test public void testSingleFlowable() { - Flowable<Integer> observable = Flowable.just(1).singleElement().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1).singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleWithTooManyElementsFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2).singleElement().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1, 2).singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError( + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError( isA(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleWithEmptyFlowable() { - Flowable<Integer> observable = Flowable.<Integer> empty().singleElement().toFlowable(); + Flowable<Integer> flowable = Flowable.<Integer> empty().singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); inOrder.verifyNoMoreInteractions(); } @@ -192,10 +191,9 @@ public void onNext(Integer t) { assertEquals(Arrays.asList(Long.MAX_VALUE), requests); } - @Test public void testSingleWithPredicateFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2) + Flowable<Integer> flowable = Flowable.just(1, 2) .filter( new Predicate<Integer>() { @@ -206,18 +204,18 @@ public boolean test(Integer t1) { }) .singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleWithPredicateAndTooManyElementsFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4) + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4) .filter( new Predicate<Integer>() { @@ -228,18 +226,18 @@ public boolean test(Integer t1) { }) .singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError( + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError( isA(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleWithPredicateAndEmptyFlowable() { - Flowable<Integer> observable = Flowable.just(1) + Flowable<Integer> flowable = Flowable.just(1) .filter( new Predicate<Integer>() { @@ -249,58 +247,58 @@ public boolean test(Integer t1) { } }) .singleElement().toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultFlowable() { - Flowable<Integer> observable = Flowable.just(1).single(2).toFlowable(); + Flowable<Integer> flowable = Flowable.just(1).single(2).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultWithTooManyElementsFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2).single(3).toFlowable(); + Flowable<Integer> flowable = Flowable.just(1, 2).single(3).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError( + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError( isA(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultWithEmptyFlowable() { - Flowable<Integer> observable = Flowable.<Integer> empty() + Flowable<Integer> flowable = Flowable.<Integer> empty() .single(1).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(1); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(1); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultWithPredicateFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2) + Flowable<Integer> flowable = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -309,18 +307,18 @@ public boolean test(Integer t1) { }) .single(4).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultWithPredicateAndTooManyElementsFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2, 3, 4) + Flowable<Integer> flowable = Flowable.just(1, 2, 3, 4) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -329,18 +327,18 @@ public boolean test(Integer t1) { }) .single(6).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError( + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError( isA(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleOrDefaultWithPredicateAndEmptyFlowable() { - Flowable<Integer> observable = Flowable.just(1) + Flowable<Integer> flowable = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -349,18 +347,18 @@ public boolean test(Integer t1) { }) .single(2).toFlowable(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testSingleWithBackpressureFlowable() { - Flowable<Integer> observable = Flowable.just(1, 2).singleElement().toFlowable(); + Flowable<Integer> flowable = Flowable.just(1, 2).singleElement().toFlowable(); Subscriber<Integer> subscriber = spy(new DefaultSubscriber<Integer>() { @@ -384,7 +382,7 @@ public void onNext(Integer integer) { request(1); } }); - observable.subscribe(subscriber); + flowable.subscribe(subscriber); InOrder inOrder = inOrder(subscriber); inOrder.verify(subscriber, times(1)).onError(isA(IllegalArgumentException.class)); @@ -393,10 +391,10 @@ public void onNext(Integer integer) { @Test public void testSingle() { - Maybe<Integer> observable = Flowable.just(1).singleElement(); + Maybe<Integer> maybe = Flowable.just(1).singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -405,10 +403,10 @@ public void testSingle() { @Test public void testSingleWithTooManyElements() { - Maybe<Integer> observable = Flowable.just(1, 2).singleElement(); + Maybe<Integer> maybe = Flowable.just(1, 2).singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError( @@ -418,10 +416,10 @@ public void testSingleWithTooManyElements() { @Test public void testSingleWithEmpty() { - Maybe<Integer> observable = Flowable.<Integer> empty().singleElement(); + Maybe<Integer> maybe = Flowable.<Integer> empty().singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer).onComplete(); @@ -476,7 +474,7 @@ public void accept(long n) { @Test public void testSingleWithPredicate() { - Maybe<Integer> observable = Flowable.just(1, 2) + Maybe<Integer> maybe = Flowable.just(1, 2) .filter( new Predicate<Integer>() { @@ -488,7 +486,7 @@ public boolean test(Integer t1) { .singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -497,7 +495,7 @@ public boolean test(Integer t1) { @Test public void testSingleWithPredicateAndTooManyElements() { - Maybe<Integer> observable = Flowable.just(1, 2, 3, 4) + Maybe<Integer> maybe = Flowable.just(1, 2, 3, 4) .filter( new Predicate<Integer>() { @@ -509,7 +507,7 @@ public boolean test(Integer t1) { .singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError( @@ -519,7 +517,7 @@ public boolean test(Integer t1) { @Test public void testSingleWithPredicateAndEmpty() { - Maybe<Integer> observable = Flowable.just(1) + Maybe<Integer> maybe = Flowable.just(1) .filter( new Predicate<Integer>() { @@ -531,7 +529,7 @@ public boolean test(Integer t1) { .singleElement(); MaybeObserver<Integer> observer = TestHelper.mockMaybeObserver(); - observable.subscribe(observer); + maybe.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer).onComplete(); @@ -541,10 +539,10 @@ public boolean test(Integer t1) { @Test public void testSingleOrDefault() { - Single<Integer> observable = Flowable.just(1).single(2); + Single<Integer> single = Flowable.just(1).single(2); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -553,10 +551,10 @@ public void testSingleOrDefault() { @Test public void testSingleOrDefaultWithTooManyElements() { - Single<Integer> observable = Flowable.just(1, 2).single(3); + Single<Integer> single = Flowable.just(1, 2).single(3); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError( @@ -566,11 +564,11 @@ public void testSingleOrDefaultWithTooManyElements() { @Test public void testSingleOrDefaultWithEmpty() { - Single<Integer> observable = Flowable.<Integer> empty() + Single<Integer> single = Flowable.<Integer> empty() .single(1); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(1); @@ -579,7 +577,7 @@ public void testSingleOrDefaultWithEmpty() { @Test public void testSingleOrDefaultWithPredicate() { - Single<Integer> observable = Flowable.just(1, 2) + Single<Integer> single = Flowable.just(1, 2) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -589,7 +587,7 @@ public boolean test(Integer t1) { .single(4); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -598,7 +596,7 @@ public boolean test(Integer t1) { @Test public void testSingleOrDefaultWithPredicateAndTooManyElements() { - Single<Integer> observable = Flowable.just(1, 2, 3, 4) + Single<Integer> single = Flowable.just(1, 2, 3, 4) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -608,7 +606,7 @@ public boolean test(Integer t1) { .single(6); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError( @@ -618,7 +616,7 @@ public boolean test(Integer t1) { @Test public void testSingleOrDefaultWithPredicateAndEmpty() { - Single<Integer> observable = Flowable.just(1) + Single<Integer> single = Flowable.just(1) .filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { @@ -628,7 +626,7 @@ public boolean test(Integer t1) { .single(2); SingleObserver<Integer> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onSuccess(2); @@ -715,9 +713,9 @@ public void singleElementOperatorDoNotSwallowExceptionWhenDone() { }); Flowable.unsafeCreate(new Publisher<Integer>() { - @Override public void subscribe(final Subscriber<? super Integer> observer) { - observer.onComplete(); - observer.onError(exception); + @Override public void subscribe(final Subscriber<? super Integer> subscriber) { + subscriber.onComplete(); + subscriber.onError(exception); } }).singleElement().test().assertComplete(); @@ -731,22 +729,22 @@ public void singleElementOperatorDoNotSwallowExceptionWhenDone() { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.singleOrError(); + public Object apply(Flowable<Object> f) throws Exception { + return f.singleOrError(); } }, false, 1, 1, 1); TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.singleElement(); + public Object apply(Flowable<Object> f) throws Exception { + return f.singleElement(); } }, false, 1, 1, 1); TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.singleOrError().toFlowable(); + public Object apply(Flowable<Object> f) throws Exception { + return f.singleOrError().toFlowable(); } }, false, 1, 1, 1); } @@ -755,16 +753,54 @@ public Object apply(Flowable<Object> o) throws Exception { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, SingleSource<Object>>() { @Override - public SingleSource<Object> apply(Flowable<Object> o) throws Exception { - return o.singleOrError(); + public SingleSource<Object> apply(Flowable<Object> f) throws Exception { + return f.singleOrError(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.singleOrError().toFlowable(); } }); TestHelper.checkDoubleOnSubscribeFlowableToMaybe(new Function<Flowable<Object>, MaybeSource<Object>>() { @Override - public MaybeSource<Object> apply(Flowable<Object> o) throws Exception { - return o.singleElement(); + public MaybeSource<Object> apply(Flowable<Object> f) throws Exception { + return f.singleElement(); + } + }); + + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.singleElement().toFlowable(); } }); } + + @Test + public void cancelAsFlowable() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp.singleOrError().toFlowable().test(); + + assertTrue(pp.hasSubscribers()); + + ts.assertEmpty(); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void singleOrError() { + Flowable.empty() + .singleOrError() + .toFlowable() + .test() + .assertFailure(NoSuchElementException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java index fb37d544b5..11680a0927 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTest.java @@ -24,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -31,72 +32,77 @@ public class FlowableSkipLastTest { @Test public void testSkipLastEmpty() { - Flowable<String> observable = Flowable.<String> empty().skipLast(2); + Flowable<String> flowable = Flowable.<String> empty().skipLast(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, never()).onNext(any(String.class)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSkipLast1() { - Flowable<String> observable = Flowable.fromIterable(Arrays.asList("one", "two", "three")).skipLast(2); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); - observable.subscribe(observer); - inOrder.verify(observer, never()).onNext("two"); - inOrder.verify(observer, never()).onNext("three"); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Flowable<String> flowable = Flowable.fromIterable(Arrays.asList("one", "two", "three")).skipLast(2); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); + flowable.subscribe(subscriber); + + inOrder.verify(subscriber, never()).onNext("two"); + inOrder.verify(subscriber, never()).onNext("three"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSkipLast2() { - Flowable<String> observable = Flowable.fromIterable(Arrays.asList("one", "two")).skipLast(2); + Flowable<String> flowable = Flowable.fromIterable(Arrays.asList("one", "two")).skipLast(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, never()).onNext(any(String.class)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSkipLastWithZeroCount() { Flowable<String> w = Flowable.just("one", "two"); - Flowable<String> observable = w.skipLast(0); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Flowable<String> flowable = w.skipLast(0); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @Ignore("Null values not allowed") public void testSkipLastWithNull() { - Flowable<String> observable = Flowable.fromIterable(Arrays.asList("one", null, "two")).skipLast(1); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext(null); - verify(observer, never()).onNext("two"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Flowable<String> flowable = Flowable.fromIterable(Arrays.asList("one", null, "two")).skipLast(1); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext(null); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSkipLastWithBackpressure() { - Flowable<Integer> o = Flowable.range(0, Flowable.bufferSize() * 2).skipLast(Flowable.bufferSize() + 10); + Flowable<Integer> f = Flowable.range(0, Flowable.bufferSize() * 2).skipLast(Flowable.bufferSize() + 10); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - o.observeOn(Schedulers.computation()).subscribe(ts); + f.observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); assertEquals((Flowable.bufferSize()) - 10, ts.valueCount()); @@ -121,4 +127,15 @@ public void error() { .assertFailure(TestException.class); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.skipLast(1); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java index ac2d820d1d..31c8294a64 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -40,9 +39,9 @@ public void testSkipLastTimed() { // FIXME the timeunit now matters due to rounding Flowable<Integer> result = source.skipLast(1000, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -57,17 +56,17 @@ public void testSkipLastTimed() { scheduler.advanceTimeBy(950, TimeUnit.MILLISECONDS); source.onComplete(); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o, never()).onNext(4); - inOrder.verify(o, never()).onNext(5); - inOrder.verify(o, never()).onNext(6); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber, never()).onNext(4); + inOrder.verify(subscriber, never()).onNext(5); + inOrder.verify(subscriber, never()).onNext(6); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -78,9 +77,9 @@ public void testSkipLastTimedErrorBeforeTime() { Flowable<Integer> result = source.skipLast(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -89,10 +88,10 @@ public void testSkipLastTimedErrorBeforeTime() { scheduler.advanceTimeBy(1050, TimeUnit.MILLISECONDS); - verify(o).onError(any(TestException.class)); + verify(subscriber).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any()); } @Test @@ -103,9 +102,9 @@ public void testSkipLastTimedCompleteBeforeTime() { Flowable<Integer> result = source.skipLast(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -115,12 +114,12 @@ public void testSkipLastTimedCompleteBeforeTime() { source.onComplete(); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -131,9 +130,9 @@ public void testSkipLastTimedWhenAllElementsAreValid() { Flowable<Integer> result = source.skipLast(1, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -143,11 +142,11 @@ public void testSkipLastTimedWhenAllElementsAreValid() { source.onComplete(); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -187,8 +186,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.skipLast(1, TimeUnit.DAYS); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.skipLast(1, TimeUnit.DAYS); } }); } @@ -196,22 +195,22 @@ public Flowable<Object> apply(Flowable<Object> o) throws Exception { @Test public void onNextDisposeRace() { TestScheduler scheduler = new TestScheduler(); - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps.skipLast(1, TimeUnit.DAYS, scheduler).test(); + final TestSubscriber<Integer> ts = pp.skipLast(1, TimeUnit.DAYS, scheduler).test(); Runnable r1 = new Runnable() { @Override public void run() { - ps.onComplete(); + pp.onComplete(); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java index 3cbf19df51..c2f8c2f40f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTest.java @@ -24,7 +24,7 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; -import io.reactivex.functions.LongConsumer; +import io.reactivex.functions.*; import io.reactivex.subscribers.TestSubscriber; public class FlowableSkipTest { @@ -34,13 +34,13 @@ public void testSkipNegativeElements() { Flowable<String> skip = Flowable.just("one", "two", "three").skip(-99); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -48,13 +48,13 @@ public void testSkipZeroElements() { Flowable<String> skip = Flowable.just("one", "two", "three").skip(0); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -62,13 +62,13 @@ public void testSkipOneElement() { Flowable<String> skip = Flowable.just("one", "two", "three").skip(1); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); - verify(observer, never()).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); + verify(subscriber, never()).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -76,13 +76,13 @@ public void testSkipTwoElements() { Flowable<String> skip = Flowable.just("one", "two", "three").skip(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); - verify(observer, never()).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); + verify(subscriber, never()).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -91,11 +91,11 @@ public void testSkipEmptyStream() { Flowable<String> w = Flowable.empty(); Flowable<String> skip = w.skip(1); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); - verify(observer, never()).onNext(any(String.class)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -104,19 +104,19 @@ public void testSkipMultipleObservers() { Flowable<String> skip = Flowable.just("one", "two", "three") .skip(2); - Subscriber<String> observer1 = TestHelper.mockSubscriber(); - skip.subscribe(observer1); + Subscriber<String> subscriber1 = TestHelper.mockSubscriber(); + skip.subscribe(subscriber1); - Subscriber<String> observer2 = TestHelper.mockSubscriber(); - skip.subscribe(observer2); + Subscriber<String> subscriber2 = TestHelper.mockSubscriber(); + skip.subscribe(subscriber2); - verify(observer1, times(1)).onNext(any(String.class)); - verify(observer1, never()).onError(any(Throwable.class)); - verify(observer1, times(1)).onComplete(); + verify(subscriber1, times(1)).onNext(any(String.class)); + verify(subscriber1, never()).onError(any(Throwable.class)); + verify(subscriber1, times(1)).onComplete(); - verify(observer2, times(1)).onNext(any(String.class)); - verify(observer2, never()).onError(any(Throwable.class)); - verify(observer2, times(1)).onComplete(); + verify(subscriber2, times(1)).onNext(any(String.class)); + verify(subscriber2, never()).onError(any(Throwable.class)); + verify(subscriber2, times(1)).onComplete(); } @Test @@ -129,12 +129,12 @@ public void testSkipError() { Flowable<String> skip = Flowable.concat(ok, error).skip(100); - Subscriber<String> observer = TestHelper.mockSubscriber(); - skip.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + skip.subscribe(subscriber); - verify(observer, never()).onNext(any(String.class)); - verify(observer, times(1)).onError(e); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, times(1)).onError(e); + verify(subscriber, never()).onComplete(); } @@ -167,7 +167,7 @@ public void testRequestOverflowDoesNotOccur() { ts.assertTerminated(); ts.assertComplete(); ts.assertNoErrors(); - assertEquals(Arrays.asList(6,7,8,9,10), ts.values()); + assertEquals(Arrays.asList(6, 7, 8, 9, 10), ts.values()); } @Test @@ -175,4 +175,15 @@ public void dispose() { TestHelper.checkDisposed(Flowable.just(1).skip(2)); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.skip(1); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTimedTest.java index 1869d74e66..0033cb4559 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipTimedTest.java @@ -36,9 +36,9 @@ public void testSkipTimed() { Flowable<Integer> result = source.skip(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -52,17 +52,17 @@ public void testSkipTimed() { source.onComplete(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(o, never()).onNext(1); - inOrder.verify(o, never()).onNext(2); - inOrder.verify(o, never()).onNext(3); - inOrder.verify(o).onNext(4); - inOrder.verify(o).onNext(5); - inOrder.verify(o).onNext(6); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber, never()).onNext(1); + inOrder.verify(subscriber, never()).onNext(2); + inOrder.verify(subscriber, never()).onNext(3); + inOrder.verify(subscriber).onNext(4); + inOrder.verify(subscriber).onNext(5); + inOrder.verify(subscriber).onNext(6); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -73,9 +73,9 @@ public void testSkipTimedFinishBeforeTime() { Flowable<Integer> result = source.skip(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -84,12 +84,12 @@ public void testSkipTimedFinishBeforeTime() { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -100,9 +100,9 @@ public void testSkipTimedErrorBeforeTime() { Flowable<Integer> result = source.skip(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -111,12 +111,12 @@ public void testSkipTimedErrorBeforeTime() { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -127,9 +127,9 @@ public void testSkipTimedErrorAfterTime() { Flowable<Integer> result = source.skip(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -143,17 +143,17 @@ public void testSkipTimedErrorAfterTime() { source.onError(new TestException()); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(o, never()).onNext(1); - inOrder.verify(o, never()).onNext(2); - inOrder.verify(o, never()).onNext(3); - inOrder.verify(o).onNext(4); - inOrder.verify(o).onNext(5); - inOrder.verify(o).onNext(6); - inOrder.verify(o).onError(any(TestException.class)); + inOrder.verify(subscriber, never()).onNext(1); + inOrder.verify(subscriber, never()).onNext(2); + inOrder.verify(subscriber, never()).onNext(3); + inOrder.verify(subscriber).onNext(4); + inOrder.verify(subscriber).onNext(5); + inOrder.verify(subscriber).onNext(6); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onComplete(); + verify(subscriber, never()).onComplete(); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipUntilTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipUntilTest.java index 00386c8a7f..a770b8520b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipUntilTest.java @@ -23,11 +23,11 @@ import io.reactivex.processors.PublishProcessor; public class FlowableSkipUntilTest { - Subscriber<Object> observer; + Subscriber<Object> subscriber; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } @Test @@ -36,7 +36,7 @@ public void normal1() { PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> m = source.skipUntil(other); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(0); source.onNext(1); @@ -48,11 +48,11 @@ public void normal1() { source.onNext(4); source.onComplete(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onNext(4); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, times(1)).onNext(4); + verify(subscriber, times(1)).onComplete(); } @Test @@ -61,7 +61,7 @@ public void otherNeverFires() { Flowable<Integer> m = source.skipUntil(Flowable.never()); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(0); source.onNext(1); @@ -70,9 +70,9 @@ public void otherNeverFires() { source.onNext(4); source.onComplete(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onNext(any()); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, times(1)).onComplete(); } @Test @@ -81,11 +81,11 @@ public void otherEmpty() { Flowable<Integer> m = source.skipUntil(Flowable.empty()); - m.subscribe(observer); + m.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onNext(any()); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -94,7 +94,7 @@ public void otherFiresAndCompletes() { PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> m = source.skipUntil(other); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(0); source.onNext(1); @@ -107,11 +107,11 @@ public void otherFiresAndCompletes() { source.onNext(4); source.onComplete(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onNext(4); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onNext(3); + verify(subscriber, times(1)).onNext(4); + verify(subscriber, times(1)).onComplete(); } @Test @@ -120,7 +120,7 @@ public void sourceThrows() { PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> m = source.skipUntil(other); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(0); source.onNext(1); @@ -131,9 +131,9 @@ public void sourceThrows() { source.onNext(2); source.onError(new RuntimeException("Forced failure")); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -142,16 +142,16 @@ public void otherThrowsImmediately() { PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> m = source.skipUntil(other); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(0); source.onNext(1); other.onError(new RuntimeException("Forced failure")); - verify(observer, never()).onNext(any()); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -163,15 +163,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.skipUntil(Flowable.never()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.skipUntil(Flowable.never()); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return Flowable.never().skipUntil(o); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return Flowable.never().skipUntil(f); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java index d098675349..0ac4a1369e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import org.junit.Test; @@ -121,16 +120,16 @@ public void testSkipManySubscribers() { Flowable<Integer> src = Flowable.range(1, 10).skipWhile(LESS_THAN_FIVE); int n = 5; for (int i = 0; i < n; i++) { - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - src.subscribe(o); + src.subscribe(subscriber); for (int j = 5; j < 10; j++) { - inOrder.verify(o).onNext(j); + inOrder.verify(subscriber).onNext(j); } - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } @@ -143,8 +142,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.skipWhile(Functions.alwaysFalse()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.skipWhile(Functions.alwaysFalse()); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOnTest.java index 7798cc88f1..6b1b24b78e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOnTest.java @@ -40,7 +40,7 @@ public void testIssue813() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch doneLatch = new CountDownLatch(1); - TestSubscriber<Integer> observer = new TestSubscriber<Integer>(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable .unsafeCreate(new Publisher<Integer>() { @@ -64,16 +64,16 @@ public void subscribe( doneLatch.countDown(); } } - }).subscribeOn(Schedulers.computation()).subscribe(observer); + }).subscribeOn(Schedulers.computation()).subscribe(ts); // wait for scheduling scheduled.await(); // trigger unsubscribe - observer.dispose(); + ts.dispose(); latch.countDown(); doneLatch.await(); - observer.assertNoErrors(); - observer.assertComplete(); + ts.assertNoErrors(); + ts.assertComplete(); } @Test @@ -294,7 +294,7 @@ public void dispose() { @Test public void deferredRequestRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java index 1607a4b524..f429d8e172 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmptyTest.java @@ -29,13 +29,12 @@ import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; - public class FlowableSwitchIfEmptyTest { @Test public void testSwitchWhenNotEmpty() throws Exception { final AtomicBoolean subscribed = new AtomicBoolean(false); - final Flowable<Integer> observable = Flowable.just(4) + final Flowable<Integer> flowable = Flowable.just(4) .switchIfEmpty(Flowable.just(2) .doOnSubscribe(new Consumer<Subscription>() { @Override @@ -44,16 +43,16 @@ public void accept(Subscription s) { } })); - assertEquals(4, observable.blockingSingle().intValue()); + assertEquals(4, flowable.blockingSingle().intValue()); assertFalse(subscribed.get()); } @Test public void testSwitchWhenEmpty() throws Exception { - final Flowable<Integer> observable = Flowable.<Integer>empty() + final Flowable<Integer> flowable = Flowable.<Integer>empty() .switchIfEmpty(Flowable.fromIterable(Arrays.asList(42))); - assertEquals(42, observable.blockingSingle().intValue()); + assertEquals(42, flowable.blockingSingle().intValue()); } @Test @@ -80,8 +79,8 @@ public void cancel() { } }); - final Flowable<Long> observable = Flowable.<Long>empty().switchIfEmpty(withProducer); - assertEquals(42, observable.blockingSingle().intValue()); + final Flowable<Long> flowable = Flowable.<Long>empty().switchIfEmpty(withProducer); + assertEquals(42, flowable.blockingSingle().intValue()); } @Test @@ -122,14 +121,13 @@ public void onNext(Long aLong) { } }).subscribe(); - assertTrue(bs.isCancelled()); // FIXME no longer assertable // assertTrue(sub.isUnsubscribed()); } @Test - public void testSwitchShouldTriggerUnsubscribe() { + public void testSwitchShouldNotTriggerUnsubscribe() { final BooleanSubscription bs = new BooleanSubscription(); Flowable.unsafeCreate(new Publisher<Long>() { @@ -139,7 +137,7 @@ public void subscribe(final Subscriber<? super Long> subscriber) { subscriber.onComplete(); } }).switchIfEmpty(Flowable.<Long>never()).subscribe(); - assertTrue(bs.isCancelled()); + assertFalse(bs.isCancelled()); } @Test @@ -156,6 +154,7 @@ public void testSwitchRequestAlternativeObservableWithBackpressure() { ts.request(1); ts.assertValueCount(3); } + @Test public void testBackpressureNoRequest() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); @@ -167,7 +166,7 @@ public void testBackpressureNoRequest() { @Test public void testBackpressureOnFirstObservable() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); - Flowable.just(1,2,3).switchIfEmpty(Flowable.just(4, 5, 6)).subscribe(ts); + Flowable.just(1, 2, 3).switchIfEmpty(Flowable.just(4, 5, 6)).subscribe(ts); ts.assertNotComplete(); ts.assertNoErrors(); ts.assertNoValues(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java index 2bad160a63..794e43e273 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -33,297 +33,297 @@ import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.TestScheduler; +import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; public class FlowableSwitchTest { private TestScheduler scheduler; private Scheduler.Worker innerScheduler; - private Subscriber<String> observer; + private Subscriber<String> subscriber; @Before public void before() { scheduler = new TestScheduler(); innerScheduler = scheduler.createWorker(); - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } @Test public void testSwitchWhenOuterCompleteBeforeInner() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 70, "one"); - publishNext(observer, 100, "two"); - publishCompleted(observer, 200); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 70, "one"); + publishNext(subscriber, 100, "two"); + publishCompleted(subscriber, 200); } })); - publishCompleted(observer, 60); + publishCompleted(subscriber, 60); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(2)).onNext(anyString()); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(2)).onNext(anyString()); + inOrder.verify(subscriber, times(1)).onComplete(); } @Test public void testSwitchWhenInnerCompleteBeforeOuter() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 10, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 10, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 0, "one"); - publishNext(observer, 10, "two"); - publishCompleted(observer, 20); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 0, "one"); + publishNext(subscriber, 10, "two"); + publishCompleted(subscriber, 20); } })); - publishNext(observer, 100, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 100, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 0, "three"); - publishNext(observer, 10, "four"); - publishCompleted(observer, 20); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 0, "three"); + publishNext(subscriber, 10, "four"); + publishCompleted(subscriber, 20); } })); - publishCompleted(observer, 200); + publishCompleted(subscriber, 200); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(150, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(1)).onNext("four"); scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyString()); + inOrder.verify(subscriber, times(1)).onComplete(); } @Test public void testSwitchWithComplete() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 60, "one"); - publishNext(observer, 100, "two"); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 60, "one"); + publishNext(subscriber, 100, "two"); } })); - publishNext(observer, 200, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 200, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 0, "three"); - publishNext(observer, 100, "four"); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 0, "three"); + publishNext(subscriber, 100, "four"); } })); - publishCompleted(observer, 250); + publishCompleted(subscriber, 250); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("two"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("four"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("four"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testSwitchWithError() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, "one"); + publishNext(subscriber, 100, "two"); } })); - publishNext(observer, 200, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 200, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 0, "three"); - publishNext(observer, 100, "four"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 0, "three"); + publishNext(subscriber, 100, "four"); } })); - publishError(observer, 250, new TestException()); + publishError(subscriber, 250, new TestException()); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("two"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onError(any(TestException.class)); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(any(TestException.class)); } @Test public void testSwitchWithSubsequenceComplete() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, "one"); + publishNext(subscriber, 100, "two"); } })); - publishNext(observer, 130, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 130, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishCompleted(observer, 0); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishCompleted(subscriber, 0); } })); - publishNext(observer, 150, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 150, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, "three"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, "three"); } })); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testSwitchWithSubsequenceError() { Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, "one"); + publishNext(subscriber, 100, "two"); } })); - publishNext(observer, 130, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 130, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishError(observer, 0, new TestException()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishError(subscriber, 0, new TestException()); } })); - publishNext(observer, 150, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 150, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 50, "three"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 50, "three"); } })); @@ -331,49 +331,49 @@ public void subscribe(Subscriber<? super String> observer) { }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext("three"); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onError(any(TestException.class)); + inOrder.verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(any(TestException.class)); } - private <T> void publishCompleted(final Subscriber<T> observer, long delay) { + private <T> void publishCompleted(final Subscriber<T> subscriber, long delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishError(final Subscriber<T> observer, long delay, final Throwable error) { + private <T> void publishError(final Subscriber<T> subscriber, long delay, final Throwable error) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onError(error); + subscriber.onError(error); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishNext(final Subscriber<T> observer, long delay, final T value) { + private <T> void publishNext(final Subscriber<T> subscriber, long delay, final T value) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } @@ -383,45 +383,45 @@ public void testSwitchIssue737() { // https://github.com/ReactiveX/RxJava/issues/737 Flowable<Flowable<String>> source = Flowable.unsafeCreate(new Publisher<Flowable<String>>() { @Override - public void subscribe(Subscriber<? super Flowable<String>> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 0, Flowable.unsafeCreate(new Publisher<String>() { + public void subscribe(Subscriber<? super Flowable<String>> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 0, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 10, "1-one"); - publishNext(observer, 20, "1-two"); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 10, "1-one"); + publishNext(subscriber, 20, "1-two"); // The following events will be ignored - publishNext(observer, 30, "1-three"); - publishCompleted(observer, 40); + publishNext(subscriber, 30, "1-three"); + publishCompleted(subscriber, 40); } })); - publishNext(observer, 25, Flowable.unsafeCreate(new Publisher<String>() { + publishNext(subscriber, 25, Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 10, "2-one"); - publishNext(observer, 20, "2-two"); - publishNext(observer, 30, "2-three"); - publishCompleted(observer, 40); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 10, "2-one"); + publishNext(subscriber, 20, "2-two"); + publishNext(subscriber, 30, "2-three"); + publishCompleted(subscriber, 40); } })); - publishCompleted(observer, 30); + publishCompleted(subscriber, 30); } }); Flowable<String> sampled = Flowable.switchOnNext(source); - sampled.subscribe(observer); + sampled.subscribe(subscriber); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("1-one"); - inOrder.verify(observer, times(1)).onNext("1-two"); - inOrder.verify(observer, times(1)).onNext("2-one"); - inOrder.verify(observer, times(1)).onNext("2-two"); - inOrder.verify(observer, times(1)).onNext("2-three"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("1-one"); + inOrder.verify(subscriber, times(1)).onNext("1-two"); + inOrder.verify(subscriber, times(1)).onNext("2-one"); + inOrder.verify(subscriber, times(1)).onNext("2-two"); + inOrder.verify(subscriber, times(1)).onNext("2-three"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -449,7 +449,6 @@ public void testBackpressure() { publishCompleted(o2, 50); publishCompleted(o3, 55); - final TestSubscriber<String> testSubscriber = new TestSubscriber<String>(); Flowable.switchOnNext(o).subscribe(new DefaultSubscriber<String>() { @@ -583,7 +582,6 @@ public Flowable<Long> apply(Long t) { assertTrue(ts.valueCount() > 0); } - @Test(timeout = 10000) public void testSecondaryRequestsDontOverflow() throws InterruptedException { TestSubscriber<Long> ts = new TestSubscriber<Long>(0L); @@ -723,27 +721,27 @@ public void accept(Integer v) throws Exception { @Test public void switchOnNextDelayErrorWithError() { - PublishProcessor<Flowable<Integer>> ps = PublishProcessor.create(); + PublishProcessor<Flowable<Integer>> pp = PublishProcessor.create(); - TestSubscriber<Integer> ts = Flowable.switchOnNextDelayError(ps).test(); + TestSubscriber<Integer> ts = Flowable.switchOnNextDelayError(pp).test(); - ps.onNext(Flowable.just(1)); - ps.onNext(Flowable.<Integer>error(new TestException())); - ps.onNext(Flowable.range(2, 4)); - ps.onComplete(); + pp.onNext(Flowable.just(1)); + pp.onNext(Flowable.<Integer>error(new TestException())); + pp.onNext(Flowable.range(2, 4)); + pp.onComplete(); ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); } @Test public void switchOnNextDelayErrorBufferSize() { - PublishProcessor<Flowable<Integer>> ps = PublishProcessor.create(); + PublishProcessor<Flowable<Integer>> pp = PublishProcessor.create(); - TestSubscriber<Integer> ts = Flowable.switchOnNextDelayError(ps, 2).test(); + TestSubscriber<Integer> ts = Flowable.switchOnNextDelayError(pp, 2).test(); - ps.onNext(Flowable.just(1)); - ps.onNext(Flowable.range(2, 4)); - ps.onComplete(); + pp.onNext(Flowable.just(1)); + pp.onNext(Flowable.range(2, 4)); + pp.onComplete(); ts.assertResult(1, 2, 3, 4, 5); } @@ -821,18 +819,18 @@ public void dispose() { @Test public void nextSourceErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - ps1.switchMap(new Function<Integer, Flowable<Integer>>() { + pp1.switchMap(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { if (v == 1) { - return ps2; + return pp2; } return Flowable.never(); } @@ -842,7 +840,7 @@ public Flowable<Integer> apply(Integer v) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onNext(2); + pp1.onNext(2); } }; @@ -851,7 +849,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex); + pp2.onError(ex); } }; @@ -868,18 +866,18 @@ public void run() { @Test public void outerInnerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - ps1.switchMap(new Function<Integer, Flowable<Integer>>() { + pp1.switchMap(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { if (v == 1) { - return ps2; + return pp2; } return Flowable.never(); } @@ -891,7 +889,7 @@ public Flowable<Integer> apply(Integer v) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex1); + pp1.onError(ex1); } }; @@ -900,7 +898,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex2); + pp2.onError(ex2); } }; @@ -917,10 +915,10 @@ public void run() { @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps1.switchMap(new Function<Integer, Flowable<Integer>>() { + final TestSubscriber<Integer> ts = pp1.switchMap(new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return Flowable.never(); @@ -931,14 +929,14 @@ public Flowable<Integer> apply(Integer v) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - ps1.onNext(2); + pp1.onNext(2); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -965,11 +963,11 @@ public void badMainSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .switchMap(Functions.justFunction(Flowable.never())) @@ -1005,12 +1003,12 @@ public void badInnerSource() { Flowable.just(1).hide() .switchMap(Functions.justFunction(new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException()); - observer.onComplete(); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException()); + subscriber.onComplete(); + subscriber.onError(new TestException()); + subscriber.onComplete(); } })) .test() @@ -1024,44 +1022,44 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { @Test public void innerCompletesReentrant() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); - ps.onComplete(); + pp.onComplete(); } }; Flowable.just(1).hide() - .switchMap(Functions.justFunction(ps)) - .subscribe(to); + .switchMap(Functions.justFunction(pp)) + .subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertResult(1); + ts.assertResult(1); } @Test public void innerErrorsReentrant() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); - ps.onError(new TestException()); + pp.onError(new TestException()); } }; Flowable.just(1).hide() - .switchMap(Functions.justFunction(ps)) - .subscribe(to); + .switchMap(Functions.justFunction(pp)) + .subscribe(ts); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(TestException.class, 1); + ts.assertFailure(TestException.class, 1); } @Test @@ -1114,7 +1112,7 @@ protected void subscribeActual(Subscriber<? super Integer> s) { @Test public void drainCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final PublishProcessor<Integer> pp = PublishProcessor.create(); @@ -1144,13 +1142,162 @@ public void run() { @Test public void fusedInnerCrash() { Flowable.just(1).hide() - .switchMap(Functions.justFunction(Flowable.just(1).map(new Function<Integer, Object>() { + .switchMap(Functions.justFunction(Flowable.just(1) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.<Object>flowableStripBoundary()) + ) + ) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerCancelledOnMainError() { + final PublishProcessor<Integer> main = PublishProcessor.create(); + final PublishProcessor<Integer> inner = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.switchMap(Functions.justFunction(inner)) + .test(); + + assertTrue(main.hasSubscribers()); + + main.onNext(1); + + assertTrue(inner.hasSubscribers()); + + main.onError(new TestException()); + + assertFalse(inner.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + public void fusedBoundary() { + String thread = Thread.currentThread().getName(); + + Flowable.range(1, 10000) + .switchMap(new Function<Integer, Flowable<? extends Object>>() { @Override - public Object apply(Integer v) throws Exception { - throw new TestException(); + public Flowable<? extends Object> apply(Integer v) + throws Exception { + return Flowable.just(2).hide() + .observeOn(Schedulers.single()) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer w) throws Exception { + return Thread.currentThread().getName(); + } + }); } - }))) + }) .test() - .assertFailure(TestException.class); + .awaitDone(5, TimeUnit.SECONDS) + .assertNever(thread) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void switchMapFusedIterable() { + Flowable.range(1, 2) + .switchMap(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.fromIterable(Arrays.asList(v * 10)); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void switchMapHiddenIterable() { + Flowable.range(1, 2) + .switchMap(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.fromIterable(Arrays.asList(v * 10)).hide(); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void cancellationShouldTriggerInnerCancellationRace() throws Throwable { + final AtomicInteger outer = new AtomicInteger(); + final AtomicInteger inner = new AtomicInteger(); + + int n = 10000; + for (int i = 0; i < n; i++) { + Flowable.<Integer>create(new FlowableOnSubscribe<Integer>() { + @Override + public void subscribe(FlowableEmitter<Integer> it) + throws Exception { + it.onNext(0); + } + }, BackpressureStrategy.MISSING) + .switchMap(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) + throws Exception { + return createFlowable(inner); + } + }) + .observeOn(Schedulers.computation()) + .doFinally(new Action() { + @Override + public void run() throws Exception { + outer.incrementAndGet(); + } + }) + .take(1) + .blockingSubscribe(Functions.emptyConsumer(), new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + e.printStackTrace(); + } + }); + } + + Thread.sleep(100); + assertEquals(inner.get(), outer.get()); + assertEquals(n, inner.get()); + } + + Flowable<Integer> createFlowable(final AtomicInteger inner) { + return Flowable.<Integer>unsafeCreate(new Publisher<Integer>() { + @Override + public void subscribe(Subscriber<? super Integer> s) { + final SerializedSubscriber<Integer> it = new SerializedSubscriber<Integer>(s); + it.onSubscribe(new BooleanSubscription()); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(1); + } + }, 0, TimeUnit.MILLISECONDS); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(2); + } + }, 0, TimeUnit.MILLISECONDS); + } + }) + .doFinally(new Action() { + @Override + public void run() throws Exception { + inner.incrementAndGet(); + } + }); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java index 2d31e46223..5a94904936 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOneTest.java @@ -91,7 +91,7 @@ public void testLastWithBackpressure() { public void testTakeLastZeroProcessesAllItemsButIgnoresThem() { final AtomicInteger upstreamCount = new AtomicInteger(); final int num = 10; - long count = Flowable.range(1,num).doOnNext(new Consumer<Integer>() { + long count = Flowable.range(1, num).doOnNext(new Consumer<Integer>() { @Override public void accept(Integer t) { @@ -118,7 +118,9 @@ public void requestMore(long n) { @Override public void onStart() { - request(initialRequest); + if (initialRequest > 0) { + request(initialRequest); + } } @Override diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java index 9ae4bfd380..3320650881 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java @@ -24,7 +24,6 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.schedulers.Schedulers; @@ -37,11 +36,12 @@ public void testTakeLastEmpty() { Flowable<String> w = Flowable.empty(); Flowable<String> take = w.takeLast(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, never()).onNext(any(String.class)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -49,14 +49,15 @@ public void testTakeLast1() { Flowable<String> w = Flowable.just("one", "two", "three"); Flowable<String> take = w.takeLast(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(observer); - take.subscribe(observer); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onNext("one"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); + take.subscribe(subscriber); + + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onNext("one"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -64,11 +65,12 @@ public void testTakeLast2() { Flowable<String> w = Flowable.just("one"); Flowable<String> take = w.takeLast(10); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -76,11 +78,12 @@ public void testTakeLastWithZeroCount() { Flowable<String> w = Flowable.just("one"); Flowable<String> take = w.takeLast(0); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, never()).onNext("one"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, never()).onNext("one"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -89,13 +92,14 @@ public void testTakeLastWithNull() { Flowable<String> w = Flowable.just("one", null, "three"); Flowable<String> take = w.takeLast(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, never()).onNext("one"); - verify(observer, times(1)).onNext(null); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, never()).onNext("one"); + verify(subscriber, times(1)).onNext(null); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test(expected = IndexOutOfBoundsException.class) @@ -236,7 +240,6 @@ public void onNext(Integer integer) { }); } - @Test public void testIgnoreRequest4() { // If `takeLast` does not ignore `request` properly, StackOverflowError will be thrown. @@ -288,7 +291,7 @@ public void onNext(Integer integer) { cancel(); } }); - assertEquals(1,count.get()); + assertEquals(1, count.get()); } @Test(timeout = 10000) @@ -328,8 +331,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.takeLast(5); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.takeLast(5); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java index 88015beffe..5f2cda82ea 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java @@ -44,11 +44,11 @@ public void takeLastTimed() { // FIXME time unit now matters! Flowable<Object> result = source.takeLast(1000, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); // T: 0ms scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); @@ -62,13 +62,13 @@ public void takeLastTimed() { scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); source.onComplete(); // T: 1250ms - inOrder.verify(o, times(1)).onNext(2); - inOrder.verify(o, times(1)).onNext(3); - inOrder.verify(o, times(1)).onNext(4); - inOrder.verify(o, times(1)).onNext(5); - inOrder.verify(o, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onNext(3); + inOrder.verify(subscriber, times(1)).onNext(4); + inOrder.verify(subscriber, times(1)).onNext(5); + inOrder.verify(subscriber, times(1)).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -80,11 +80,11 @@ public void takeLastTimedDelayCompletion() { // FIXME time unit now matters Flowable<Object> result = source.takeLast(1000, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); // T: 0ms scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); @@ -98,10 +98,10 @@ public void takeLastTimedDelayCompletion() { scheduler.advanceTimeBy(1250, TimeUnit.MILLISECONDS); source.onComplete(); // T: 2250ms - inOrder.verify(o, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -113,11 +113,11 @@ public void takeLastTimedWithCapacity() { // FIXME time unit now matters! Flowable<Object> result = source.takeLast(2, 1000, TimeUnit.MILLISECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); // T: 0ms scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); @@ -131,11 +131,11 @@ public void takeLastTimedWithCapacity() { scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); source.onComplete(); // T: 1250ms - inOrder.verify(o, times(1)).onNext(4); - inOrder.verify(o, times(1)).onNext(5); - inOrder.verify(o, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext(4); + inOrder.verify(subscriber, times(1)).onNext(5); + inOrder.verify(subscriber, times(1)).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -146,11 +146,11 @@ public void takeLastTimedThrowingSource() { Flowable<Object> result = source.takeLast(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); // T: 0ms scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); @@ -164,10 +164,10 @@ public void takeLastTimedThrowingSource() { scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); source.onError(new TestException()); // T: 1250ms - inOrder.verify(o, times(1)).onError(any(TestException.class)); + inOrder.verify(subscriber, times(1)).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @Test @@ -178,11 +178,11 @@ public void takeLastTimedWithZeroCapacity() { Flowable<Object> result = source.takeLast(0, 1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + InOrder inOrder = inOrder(subscriber); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); // T: 0ms scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); @@ -196,10 +196,10 @@ public void takeLastTimedWithZeroCapacity() { scheduler.advanceTimeBy(250, TimeUnit.MILLISECONDS); source.onComplete(); // T: 1250ms - inOrder.verify(o, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -208,19 +208,19 @@ public void testContinuousDelivery() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - ps.takeLast(1000, TimeUnit.MILLISECONDS, scheduler).subscribe(ts); + pp.takeLast(1000, TimeUnit.MILLISECONDS, scheduler).subscribe(ts); - ps.onNext(1); + pp.onNext(1); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - ps.onNext(2); + pp.onNext(2); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - ps.onNext(3); + pp.onNext(3); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - ps.onNext(4); + pp.onNext(4); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); - ps.onComplete(); + pp.onComplete(); scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); ts.assertNoValues(); @@ -291,22 +291,22 @@ public void observeOn() { @Test public void cancelCompleteRace() { - for (int i = 0; i < 500; i++) { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = ps.takeLast(1, TimeUnit.DAYS).test(); + final TestSubscriber<Integer> ts = pp.takeLast(1, TimeUnit.DAYS).test(); Runnable r1 = new Runnable() { @Override public void run() { - ps.onComplete(); + pp.onComplete(); } }; Runnable r2 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -336,4 +336,27 @@ public Publisher<Object> apply(Flowable<Object> f) throws Exception { public void badRequest() { TestHelper.assertBadRequestReported(PublishProcessor.create().takeLast(1, TimeUnit.SECONDS)); } + + @Test + public void lastWindowIsFixedInTime() { + TimesteppingScheduler scheduler = new TimesteppingScheduler(); + scheduler.stepEnabled = false; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp + .takeLast(2, TimeUnit.SECONDS, scheduler) + .test(); + + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); + + scheduler.stepEnabled = true; + + pp.onComplete(); + + ts.assertResult(1, 2, 3, 4); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java index 653b7f034f..59d049a7c0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java @@ -16,7 +16,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -28,6 +28,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -39,13 +40,14 @@ public void testTake1() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); Flowable<String> take = w.take(2); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -53,13 +55,14 @@ public void testTake2() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); Flowable<String> take = w.take(1); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test(expected = IllegalArgumentException.class) @@ -83,10 +86,11 @@ public Integer apply(Integer t1) { } }); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - w.subscribe(observer); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class)); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + w.subscribe(subscriber); + + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError(any(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @@ -99,34 +103,42 @@ public Integer apply(Integer t1) { } }); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - w.subscribe(observer); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class)); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + w.subscribe(subscriber); + + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onError(any(IllegalArgumentException.class)); inOrder.verifyNoMoreInteractions(); } @Test public void testTakeDoesntLeakErrors() { - Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { - @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onError(new Throwable("test failed")); - } - }); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { + @Override + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onError(new Throwable("test failed")); + } + }); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Subscriber<String> observer = TestHelper.mockSubscriber(); + source.take(1).subscribe(subscriber); - source.take(1).subscribe(observer); + verify(subscriber).onSubscribe((Subscription)notNull()); + verify(subscriber, times(1)).onNext("one"); + // even though onError is called we take(1) so shouldn't see it + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verifyNoMoreInteractions(subscriber); - verify(observer).onSubscribe((Subscription)notNull()); - verify(observer, times(1)).onNext("one"); - // even though onError is called we take(1) so shouldn't see it - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verifyNoMoreInteractions(observer); + TestHelper.assertUndeliverable(errors, 0, Throwable.class, "test failed"); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -136,24 +148,25 @@ public void testTakeZeroDoesntLeakError() { final BooleanSubscription bs = new BooleanSubscription(); Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { + public void subscribe(Subscriber<? super String> subscriber) { subscribed.set(true); - observer.onSubscribe(bs); - observer.onError(new Throwable("test failed")); + subscriber.onSubscribe(bs); + subscriber.onError(new Throwable("test failed")); } }); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + + source.take(0).subscribe(subscriber); - source.take(0).subscribe(observer); assertTrue("source subscribed", subscribed.get()); assertTrue("source unsubscribed", bs.isCancelled()); - verify(observer, never()).onNext(anyString()); + verify(subscriber, never()).onNext(anyString()); // even though onError is called we take(0) so shouldn't see it - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verifyNoMoreInteractions(observer); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verifyNoMoreInteractions(subscriber); } @Test @@ -161,10 +174,10 @@ public void testUnsubscribeAfterTake() { TestFlowableFunc f = new TestFlowableFunc("one", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(f); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> take = w.take(1); - take.subscribe(observer); + take.subscribe(subscriber); // wait for the Flowable to complete try { @@ -175,14 +188,14 @@ public void testUnsubscribeAfterTake() { } System.out.println("TestFlowable thread finished"); - verify(observer).onSubscribe((Subscription)notNull()); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, times(1)).onComplete(); + verify(subscriber).onSubscribe((Subscription)notNull()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, times(1)).onComplete(); // FIXME no longer assertable // verify(s, times(1)).unsubscribe(); - verifyNoMoreInteractions(observer); + verifyNoMoreInteractions(subscriber); } @Test(timeout = 2000) @@ -238,8 +251,8 @@ static class TestFlowableFunc implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("TestFlowable subscribed to ..."); t = new Thread(new Runnable() { @@ -249,9 +262,9 @@ public void run() { System.out.println("running TestFlowable thread"); for (String s : values) { System.out.println("TestFlowable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { throw new RuntimeException(e); } @@ -281,18 +294,18 @@ public void subscribe(Subscriber<? super Long> op) { @Test(timeout = 2000) public void testTakeObserveOn() { - Subscriber<Object> o = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); INFINITE_OBSERVABLE.onBackpressureDrop() .observeOn(Schedulers.newThread()).take(1).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); - verify(o).onNext(1L); - verify(o, never()).onNext(2L); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1L); + verify(subscriber, never()).onNext(2L); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -437,7 +450,6 @@ public void accept(Integer v) { ts.assertComplete(); } - @Test public void takeNegative() { try { @@ -465,10 +477,48 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.take(2); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.take(2); } }); } + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.never().take(1)); + } + + @Test + public void requestRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final TestSubscriber<Integer> ts = Flowable.range(1, 2).take(2).test(0L); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r1); + + ts.assertResult(1, 2); + } + } + + @Test + public void errorAfterLimitReached() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable.error(new TestException()) + .take(0) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTimedTest.java index cc04d04b6b..db1132e86c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTimedTest.java @@ -36,9 +36,9 @@ public void testTakeTimed() { Flowable<Integer> result = source.take(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -48,15 +48,15 @@ public void testTakeTimed() { source.onNext(4); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(4); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(4); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -67,9 +67,9 @@ public void testTakeTimedErrorBeforeTime() { Flowable<Integer> result = source.take(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -80,15 +80,15 @@ public void testTakeTimedErrorBeforeTime() { source.onNext(4); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onError(any(TestException.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onComplete(); - verify(o, never()).onNext(4); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(4); } @Test @@ -99,9 +99,9 @@ public void testTakeTimedErrorAfterTime() { Flowable<Integer> result = source.take(1, TimeUnit.SECONDS, scheduler); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -112,15 +112,15 @@ public void testTakeTimedErrorAfterTime() { source.onNext(4); source.onError(new TestException()); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onNext(4); - verify(o, never()).onError(any(TestException.class)); + verify(subscriber, never()).onNext(4); + verify(subscriber, never()).onError(any(TestException.class)); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java index 4060487c64..72e08bd1b3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -34,54 +33,57 @@ public class FlowableTakeUntilPredicateTest { @Test public void takeEmpty() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Flowable.empty().takeUntil(new Predicate<Object>() { @Override public boolean test(Object v) { return true; } - }).subscribe(o); + }).subscribe(subscriber); - verify(o, never()).onNext(any()); - verify(o, never()).onError(any(Throwable.class)); - verify(o).onComplete(); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); } + @Test public void takeAll() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Flowable.just(1, 2).takeUntil(new Predicate<Integer>() { @Override public boolean test(Integer v) { return false; } - }).subscribe(o); + }).subscribe(subscriber); - verify(o).onNext(1); - verify(o).onNext(2); - verify(o, never()).onError(any(Throwable.class)); - verify(o).onComplete(); + verify(subscriber).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); } + @Test public void takeFirst() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Flowable.just(1, 2).takeUntil(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; } - }).subscribe(o); + }).subscribe(subscriber); - verify(o).onNext(1); - verify(o, never()).onNext(2); - verify(o, never()).onError(any(Throwable.class)); - verify(o).onComplete(); + verify(subscriber).onNext(1); + verify(subscriber, never()).onNext(2); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); } + @Test public void takeSome() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Flowable.just(1, 2, 3).takeUntil(new Predicate<Integer>() { @Override @@ -89,17 +91,18 @@ public boolean test(Integer t1) { return t1 == 2; } }) - .subscribe(o); + .subscribe(subscriber); - verify(o).onNext(1); - verify(o).onNext(2); - verify(o, never()).onNext(3); - verify(o, never()).onError(any(Throwable.class)); - verify(o).onComplete(); + verify(subscriber).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber, never()).onNext(3); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); } + @Test public void functionThrows() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Predicate<Integer> predicate = new Predicate<Integer>() { @Override @@ -107,17 +110,18 @@ public boolean test(Integer t1) { throw new TestException("Forced failure"); } }; - Flowable.just(1, 2, 3).takeUntil(predicate).subscribe(o); + Flowable.just(1, 2, 3).takeUntil(predicate).subscribe(subscriber); - verify(o).onNext(1); - verify(o, never()).onNext(2); - verify(o, never()).onNext(3); - verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + verify(subscriber).onNext(1); + verify(subscriber, never()).onNext(2); + verify(subscriber, never()).onNext(3); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } + @Test public void sourceThrows() { - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); Flowable.just(1) .concatWith(Flowable.<Integer>error(new TestException())) @@ -127,13 +131,14 @@ public void sourceThrows() { public boolean test(Integer v) { return false; } - }).subscribe(o); + }).subscribe(subscriber); - verify(o).onNext(1); - verify(o, never()).onNext(2); - verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + verify(subscriber).onNext(1); + verify(subscriber, never()).onNext(2); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } + @Test public void backpressure() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(5L); @@ -178,8 +183,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.takeUntil(Functions.alwaysFalse()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.takeUntil(Functions.alwaysFalse()); } }); } @@ -190,12 +195,12 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onNext(1); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .takeUntil(Functions.alwaysFalse()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java index cb00a08030..b254ecdf8d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilTest.java @@ -20,6 +20,8 @@ import org.reactivestreams.*; import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.TestSubscriber; @@ -153,32 +155,32 @@ public void testTakeUntilOtherCompleted() { private static class TestObservable implements Publisher<String> { - Subscriber<? super String> observer; - Subscription s; + Subscriber<? super String> subscriber; + Subscription upstream; TestObservable(Subscription s) { - this.s = s; + this.upstream = s; } /* used to simulate subscription */ public void sendOnCompleted() { - observer.onComplete(); + subscriber.onComplete(); } /* used to simulate subscription */ public void sendOnNext(String value) { - observer.onNext(value); + subscriber.onNext(value); } /* used to simulate subscription */ public void sendOnError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override - public void subscribe(Subscriber<? super String> observer) { - this.observer = observer; - observer.onSubscribe(s); + public void subscribe(Subscriber<? super String> subscriber) { + this.subscriber = subscriber; + subscriber.onSubscribe(upstream); } } @@ -207,6 +209,7 @@ public void testUntilFires() { assertFalse("Until still has observers", until.hasSubscribers()); assertFalse("TestSubscriber is unsubscribed", ts.isCancelled()); } + @Test public void testMainCompletes() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -230,6 +233,7 @@ public void testMainCompletes() { assertFalse("Until still has observers", until.hasSubscribers()); assertFalse("TestSubscriber is unsubscribed", ts.isCancelled()); } + @Test public void testDownstreamUnsubscribes() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -282,4 +286,143 @@ public void testBackpressure() { public void dispose() { TestHelper.checkDisposed(PublishProcessor.create().takeUntil(Flowable.never())); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Flowable<Integer> c) throws Exception { + return c.takeUntil(Flowable.never()); + } + }); + } + + @Test + public void untilPublisherMainSuccess() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + main.onNext(1); + main.onNext(2); + main.onComplete(); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertResult(1, 2); + } + + @Test + public void untilPublisherMainComplete() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + main.onComplete(); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertResult(); + } + + @Test + public void untilPublisherMainError() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + main.onError(new TestException()); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + public void untilPublisherOtherOnNext() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + other.onNext(1); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertResult(); + } + + @Test + public void untilPublisherOtherOnComplete() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + other.onComplete(); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertResult(); + } + + @Test + public void untilPublisherOtherError() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + other.onError(new TestException()); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + public void untilPublisherDispose() { + PublishProcessor<Integer> main = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestSubscriber<Integer> ts = main.takeUntil(other).test(); + + assertTrue("Main no subscribers?", main.hasSubscribers()); + assertTrue("Other no subscribers?", other.hasSubscribers()); + + ts.dispose(); + + assertFalse("Main has subscribers?", main.hasSubscribers()); + assertFalse("Other has subscribers?", other.hasSubscribers()); + + ts.assertEmpty(); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java index 4ad532f94f..f99b7d1d86 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java @@ -14,9 +14,10 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.util.List; + import org.junit.*; import org.reactivestreams.*; @@ -25,6 +26,7 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.subscribers.TestSubscriber; @@ -40,13 +42,14 @@ public boolean test(Integer input) { } }); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, never()).onNext(3); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, never()).onNext(3); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -59,8 +62,8 @@ public boolean test(Integer input) { } }); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); s.onNext(1); s.onNext(2); @@ -69,13 +72,13 @@ public boolean test(Integer input) { s.onNext(5); s.onComplete(); - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, never()).onNext(3); - verify(observer, never()).onNext(4); - verify(observer, never()).onNext(5); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(1); + verify(subscriber, times(1)).onNext(2); + verify(subscriber, never()).onNext(3); + verify(subscriber, never()).onNext(4); + verify(subscriber, never()).onNext(5); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -90,32 +93,40 @@ public boolean test(String input) { } }); - Subscriber<String> observer = TestHelper.mockSubscriber(); - take.subscribe(observer); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + take.subscribe(subscriber); + + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, never()).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testTakeWhileDoesntLeakErrors() { - Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { - @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("one"); - observer.onError(new Throwable("test failed")); - } - }); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { + @Override + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("one"); + subscriber.onError(new TestException("test failed")); + } + }); - source.takeWhile(new Predicate<String>() { - @Override - public boolean test(String s) { - return false; - } - }).blockingLast(""); + source.takeWhile(new Predicate<String>() { + @Override + public boolean test(String s) { + return false; + } + }).blockingLast(""); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "test failed"); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -123,7 +134,7 @@ public void testTakeWhileProtectsPredicateCall() { TestFlowable source = new TestFlowable(mock(Subscription.class), "one"); final RuntimeException testException = new RuntimeException("test exception"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> take = Flowable.unsafeCreate(source) .takeWhile(new Predicate<String>() { @Override @@ -131,7 +142,7 @@ public boolean test(String s) { throw testException; } }); - take.subscribe(observer); + take.subscribe(subscriber); // wait for the Flowable to complete try { @@ -141,8 +152,8 @@ public boolean test(String s) { fail(e.getMessage()); } - verify(observer, never()).onNext(any(String.class)); - verify(observer, times(1)).onError(testException); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, times(1)).onError(testException); } @Test @@ -150,7 +161,7 @@ public void testUnsubscribeAfterTake() { Subscription s = mock(Subscription.class); TestFlowable w = new TestFlowable(s, "one", "two", "three"); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> take = Flowable.unsafeCreate(w) .takeWhile(new Predicate<String>() { int index; @@ -160,7 +171,7 @@ public boolean test(String s) { return index++ < 1; } }); - take.subscribe(observer); + take.subscribe(subscriber); // wait for the Flowable to complete try { @@ -171,27 +182,27 @@ public boolean test(String s) { } System.out.println("TestFlowable thread finished"); - verify(observer, times(1)).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, never()).onNext("three"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onNext("three"); verify(s, times(1)).cancel(); } private static class TestFlowable implements Publisher<String> { - final Subscription s; + final Subscription upstream; final String[] values; Thread t; TestFlowable(Subscription s, String... values) { - this.s = s; + this.upstream = s; this.values = values; } @Override - public void subscribe(final Subscriber<? super String> observer) { + public void subscribe(final Subscriber<? super String> subscriber) { System.out.println("TestFlowable subscribed to ..."); - observer.onSubscribe(s); + subscriber.onSubscribe(upstream); t = new Thread(new Runnable() { @Override @@ -200,9 +211,9 @@ public void run() { System.out.println("running TestFlowable thread"); for (String s : values) { System.out.println("TestFlowable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { throw new RuntimeException(e); } @@ -280,8 +291,8 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.takeWhile(Functions.alwaysTrue()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.takeWhile(Functions.alwaysTrue()); } }); } @@ -290,10 +301,10 @@ public Flowable<Object> apply(Flowable<Object> o) throws Exception { public void badSource() { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onComplete(); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onComplete(); } } .takeWhile(Functions.alwaysTrue()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java index 278b98a53a..12fc35760e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -34,40 +33,40 @@ public class FlowableThrottleFirstTest { private TestScheduler scheduler; private Scheduler.Worker innerScheduler; - private Subscriber<String> observer; + private Subscriber<String> subscriber; @Before public void before() { scheduler = new TestScheduler(); innerScheduler = scheduler.createWorker(); - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } @Test public void testThrottlingWithCompleted() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - publishNext(observer, 100, "one"); // publish as it's first - publishNext(observer, 300, "two"); // skip as it's last within the first 400 - publishNext(observer, 900, "three"); // publish - publishNext(observer, 905, "four"); // skip - publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + publishNext(subscriber, 100, "one"); // publish as it's first + publishNext(subscriber, 300, "two"); // skip as it's last within the first 400 + publishNext(subscriber, 900, "three"); // publish + publishNext(subscriber, 905, "four"); // skip + publishCompleted(subscriber, 1000); // Should be published as soon as the timeout expires. } }); Flowable<String> sampled = source.throttleFirst(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(0)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(0)).onNext("four"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(0)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, times(0)).onNext("four"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -75,59 +74,59 @@ public void subscribe(Subscriber<? super String> observer) { public void testThrottlingWithError() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); Exception error = new TestException(); - publishNext(observer, 100, "one"); // Should be published since it is first - publishNext(observer, 200, "two"); // Should be skipped since onError will arrive before the timeout expires - publishError(observer, 300, error); // Should be published as soon as the timeout expires. + publishNext(subscriber, 100, "one"); // Should be published since it is first + publishNext(subscriber, 200, "two"); // Should be skipped since onError will arrive before the timeout expires + publishError(subscriber, 300, error); // Should be published as soon as the timeout expires. } }); Flowable<String> sampled = source.throttleFirst(400, TimeUnit.MILLISECONDS, scheduler); - sampled.subscribe(observer); + sampled.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); scheduler.advanceTimeTo(400, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onNext("one"); - inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verify(subscriber).onNext("one"); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); } - private <T> void publishCompleted(final Subscriber<T> observer, long delay) { + private <T> void publishCompleted(final Subscriber<T> subscriber, long delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishError(final Subscriber<T> observer, long delay, final Exception error) { + private <T> void publishError(final Subscriber<T> subscriber, long delay, final Exception error) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onError(error); + subscriber.onError(error); } }, delay, TimeUnit.MILLISECONDS); } - private <T> void publishNext(final Subscriber<T> observer, long delay, final T value) { + private <T> void publishNext(final Subscriber<T> subscriber, long delay, final T value) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } @Test public void testThrottle() { - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); TestScheduler s = new TestScheduler(); PublishProcessor<Integer> o = PublishProcessor.create(); - o.throttleFirst(500, TimeUnit.MILLISECONDS, s).subscribe(observer); + o.throttleFirst(500, TimeUnit.MILLISECONDS, s).subscribe(subscriber); // send events with simulated time increments s.advanceTimeTo(0, TimeUnit.MILLISECONDS); @@ -145,15 +144,14 @@ public void testThrottle() { s.advanceTimeTo(1501, TimeUnit.MILLISECONDS); o.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onNext(1); - inOrder.verify(observer).onNext(3); - inOrder.verify(observer).onNext(7); - inOrder.verify(observer).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onNext(7); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } - @Test public void throttleFirstDefaultScheduler() { Flowable.just(1).throttleFirst(100, TimeUnit.MILLISECONDS) @@ -173,14 +171,14 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onComplete(); - observer.onNext(3); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onComplete(); + subscriber.onNext(3); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .throttleFirst(1, TimeUnit.DAYS) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java new file mode 100644 index 0000000000..5612307c28 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatestTest.java @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableThrottleLatestTest { + + @Test + public void just() { + Flowable.just(1) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertResult(1); + } + + @Test + public void range() { + Flowable.range(1, 5) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertResult(1); + } + + @Test + public void rangeEmitLatest() { + Flowable.range(1, 5) + .throttleLatest(1, TimeUnit.MINUTES, true) + .test() + .assertResult(1, 5); + } + + @Test + public void error() { + Flowable.error(new TestException()) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) throws Exception { + return f.throttleLatest(1, TimeUnit.MINUTES); + } + }); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported( + Flowable.never() + .throttleLatest(1, TimeUnit.MINUTES) + ); + } + + @Test + public void normal() { + TestScheduler sch = new TestScheduler(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp.throttleLatest(1, TimeUnit.SECONDS, sch).test(); + + pp.onNext(1); + + ts.assertValuesOnly(1); + + pp.onNext(2); + + ts.assertValuesOnly(1); + + pp.onNext(3); + + ts.assertValuesOnly(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3); + + pp.onNext(4); + + ts.assertValuesOnly(1, 3); + + pp.onNext(5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3, 5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3, 5); + + pp.onNext(6); + + ts.assertValuesOnly(1, 3, 5, 6); + + pp.onNext(7); + pp.onComplete(); + + ts.assertResult(1, 3, 5, 6); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertResult(1, 3, 5, 6); + } + + @Test + public void normalEmitLast() { + TestScheduler sch = new TestScheduler(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp.throttleLatest(1, TimeUnit.SECONDS, sch, true).test(); + + pp.onNext(1); + + ts.assertValuesOnly(1); + + pp.onNext(2); + + ts.assertValuesOnly(1); + + pp.onNext(3); + + ts.assertValuesOnly(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3); + + pp.onNext(4); + + ts.assertValuesOnly(1, 3); + + pp.onNext(5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3, 5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertValuesOnly(1, 3, 5); + + pp.onNext(6); + + ts.assertValuesOnly(1, 3, 5, 6); + + pp.onNext(7); + pp.onComplete(); + + ts.assertResult(1, 3, 5, 6, 7); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertResult(1, 3, 5, 6, 7); + } + + @Test + public void missingBackpressureExceptionFirst() throws Exception { + TestScheduler sch = new TestScheduler(); + Action onCancel = mock(Action.class); + + Flowable.just(1, 2) + .doOnCancel(onCancel) + .throttleLatest(1, TimeUnit.MINUTES, sch) + .test(0) + .assertFailure(MissingBackpressureException.class); + + verify(onCancel).run(); + } + + @Test + public void missingBackpressureExceptionLatest() throws Exception { + TestScheduler sch = new TestScheduler(); + Action onCancel = mock(Action.class); + + TestSubscriber<Integer> ts = Flowable.just(1, 2) + .concatWith(Flowable.<Integer>never()) + .doOnCancel(onCancel) + .throttleLatest(1, TimeUnit.SECONDS, sch, true) + .test(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertFailure(MissingBackpressureException.class, 1); + + verify(onCancel).run(); + } + + @Test + public void missingBackpressureExceptionLatestComplete() throws Exception { + TestScheduler sch = new TestScheduler(); + Action onCancel = mock(Action.class); + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp + .doOnCancel(onCancel) + .throttleLatest(1, TimeUnit.SECONDS, sch, true) + .test(1); + + pp.onNext(1); + pp.onNext(2); + + ts.assertValuesOnly(1); + + pp.onComplete(); + + ts.assertFailure(MissingBackpressureException.class, 1); + + verify(onCancel, never()).run(); + } + + @Test + public void take() throws Exception { + Action onCancel = mock(Action.class); + + Flowable.range(1, 5) + .doOnCancel(onCancel) + .throttleLatest(1, TimeUnit.MINUTES) + .take(1) + .test() + .assertResult(1); + + verify(onCancel).run(); + } + + @Test + public void reentrantComplete() { + TestScheduler sch = new TestScheduler(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + pp.onNext(2); + } + if (t == 2) { + pp.onComplete(); + } + } + }; + + pp.throttleLatest(1, TimeUnit.SECONDS, sch).subscribe(ts); + + pp.onNext(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + ts.assertResult(1, 2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java index 10e69d885b..1dc6369aba 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeIntervalTest.java @@ -19,7 +19,7 @@ import org.junit.*; import org.mockito.InOrder; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.TestException; @@ -32,40 +32,40 @@ public class FlowableTimeIntervalTest { private static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS; - private Subscriber<Timed<Integer>> observer; + private Subscriber<Timed<Integer>> subscriber; private TestScheduler testScheduler; - private PublishProcessor<Integer> subject; - private Flowable<Timed<Integer>> observable; + private PublishProcessor<Integer> processor; + private Flowable<Timed<Integer>> flowable; @Before public void setUp() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); testScheduler = new TestScheduler(); - subject = PublishProcessor.create(); - observable = subject.timeInterval(testScheduler); + processor = PublishProcessor.create(); + flowable = processor.timeInterval(testScheduler); } @Test public void testTimeInterval() { - InOrder inOrder = inOrder(observer); - observable.subscribe(observer); + InOrder inOrder = inOrder(subscriber); + flowable.subscribe(subscriber); testScheduler.advanceTimeBy(1000, TIME_UNIT); - subject.onNext(1); + processor.onNext(1); testScheduler.advanceTimeBy(2000, TIME_UNIT); - subject.onNext(2); + processor.onNext(2); testScheduler.advanceTimeBy(3000, TIME_UNIT); - subject.onNext(3); - subject.onComplete(); + processor.onNext(3); + processor.onComplete(); - inOrder.verify(observer, times(1)).onNext( + inOrder.verify(subscriber, times(1)).onNext( new Timed<Integer>(1, 1000, TIME_UNIT)); - inOrder.verify(observer, times(1)).onNext( + inOrder.verify(subscriber, times(1)).onNext( new Timed<Integer>(2, 2000, TIME_UNIT)); - inOrder.verify(observer, times(1)).onNext( + inOrder.verify(subscriber, times(1)).onNext( new Timed<Integer>(3, 3000, TIME_UNIT)); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -137,4 +137,14 @@ public void error() { .assertFailure(TestException.class); } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Timed<Object>>>() { + @Override + public Publisher<Timed<Object>> apply(Flowable<Object> f) + throws Exception { + return f.timeInterval(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java index 0aa0318543..5f6aa08fa6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java @@ -13,8 +13,8 @@ package io.reactivex.internal.operators.flowable; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; @@ -50,23 +50,23 @@ public void setUp() { @Test public void shouldNotTimeoutIfOnNextWithinTimeout() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); withTimeout.subscribe(ts); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); - verify(observer).onNext("One"); + verify(subscriber).onNext("One"); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); ts.cancel(); } @Test public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); withTimeout.subscribe(ts); @@ -74,59 +74,57 @@ public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { underlyingSubject.onNext("One"); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("Two"); - verify(observer).onNext("Two"); + verify(subscriber).onNext("Two"); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); ts.dispose(); } @Test public void shouldTimeoutIfOnNextNotWithinTimeout() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + TestSubscriber<String> subscriber = new TestSubscriber<String>(); - withTimeout.subscribe(ts); + withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); - ts.dispose(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT)); } @Test public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - withTimeout.subscribe(observer); + TestSubscriber<String> subscriber = new TestSubscriber<String>(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); - verify(observer).onNext("One"); + subscriber.assertValue("One"); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT), "One"); ts.dispose(); } @Test public void shouldCompleteIfUnderlyingComletes() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - withTimeout.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onComplete(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - verify(observer).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); ts.dispose(); } @Test public void shouldErrorIfUnderlyingErrors() { - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - withTimeout.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + withTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onError(new UnsupportedOperationException()); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - verify(observer).onError(any(UnsupportedOperationException.class)); + verify(subscriber).onError(any(UnsupportedOperationException.class)); ts.dispose(); } @@ -135,20 +133,20 @@ public void shouldSwitchToOtherIfOnNextNotWithinTimeout() { Flowable<String> other = Flowable.just("a", "b", "c"); Flowable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); source.subscribe(ts); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); underlyingSubject.onNext("Two"); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("One"); - inOrder.verify(observer, times(1)).onNext("a"); - inOrder.verify(observer, times(1)).onNext("b"); - inOrder.verify(observer, times(1)).onNext("c"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("One"); + inOrder.verify(subscriber, times(1)).onNext("a"); + inOrder.verify(subscriber, times(1)).onNext("b"); + inOrder.verify(subscriber, times(1)).onNext("c"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); ts.dispose(); } @@ -158,20 +156,20 @@ public void shouldSwitchToOtherIfOnErrorNotWithinTimeout() { Flowable<String> other = Flowable.just("a", "b", "c"); Flowable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); source.subscribe(ts); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); underlyingSubject.onError(new UnsupportedOperationException()); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("One"); - inOrder.verify(observer, times(1)).onNext("a"); - inOrder.verify(observer, times(1)).onNext("b"); - inOrder.verify(observer, times(1)).onNext("c"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("One"); + inOrder.verify(subscriber, times(1)).onNext("a"); + inOrder.verify(subscriber, times(1)).onNext("b"); + inOrder.verify(subscriber, times(1)).onNext("c"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); ts.dispose(); } @@ -181,20 +179,20 @@ public void shouldSwitchToOtherIfOnCompletedNotWithinTimeout() { Flowable<String> other = Flowable.just("a", "b", "c"); Flowable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); source.subscribe(ts); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); underlyingSubject.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("One"); - inOrder.verify(observer, times(1)).onNext("a"); - inOrder.verify(observer, times(1)).onNext("b"); - inOrder.verify(observer, times(1)).onNext("c"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("One"); + inOrder.verify(subscriber, times(1)).onNext("a"); + inOrder.verify(subscriber, times(1)).onNext("b"); + inOrder.verify(subscriber, times(1)).onNext("c"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); ts.dispose(); } @@ -204,8 +202,8 @@ public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { PublishProcessor<String> other = PublishProcessor.create(); Flowable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); source.subscribe(ts); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); @@ -222,10 +220,10 @@ public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { other.onNext("d"); other.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("One"); - inOrder.verify(observer, times(1)).onNext("a"); - inOrder.verify(observer, times(1)).onNext("b"); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("One"); + inOrder.verify(subscriber, times(1)).onNext("a"); + inOrder.verify(subscriber, times(1)).onNext("b"); inOrder.verifyNoMoreInteractions(); } @@ -235,8 +233,7 @@ public void shouldTimeoutIfSynchronizedFlowableEmitFirstOnNextNotWithinTimeout() final CountDownLatch exit = new CountDownLatch(1); final CountDownLatch timeoutSetuped = new CountDownLatch(1); - final Subscriber<String> observer = TestHelper.mockSubscriber(); - final TestSubscriber<String> ts = new TestSubscriber<String>(observer); + final TestSubscriber<String> subscriber = new TestSubscriber<String>(); new Thread(new Runnable() { @@ -258,16 +255,14 @@ public void subscribe(Subscriber<? super String> subscriber) { } }).timeout(1, TimeUnit.SECONDS, testScheduler) - .subscribe(ts); + .subscribe(subscriber); } }).start(); timeoutSetuped.await(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.SECONDS)); exit.countDown(); // exit the thread } @@ -287,15 +282,12 @@ public void subscribe(Subscriber<? super String> subscriber) { TestScheduler testScheduler = new TestScheduler(); Flowable<String> observableWithTimeout = never.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - observableWithTimeout.subscribe(ts); + TestSubscriber<String> subscriber = new TestSubscriber<String>(); + observableWithTimeout.subscribe(subscriber); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + subscriber.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1000, TimeUnit.MILLISECONDS)); verify(s, times(1)).cancel(); } @@ -318,14 +310,14 @@ public void subscribe(Subscriber<? super String> subscriber) { Flowable<String> observableWithTimeout = immediatelyComplete.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); observableWithTimeout.subscribe(ts); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); verify(s, times(1)).cancel(); @@ -349,14 +341,14 @@ public void subscribe(Subscriber<? super String> subscriber) { Flowable<String> observableWithTimeout = immediatelyError.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); observableWithTimeout.subscribe(ts); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onError(isA(IOException.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onError(isA(IOException.class)); inOrder.verifyNoMoreInteractions(); verify(s, times(1)).cancel(); @@ -364,18 +356,18 @@ public void subscribe(Subscriber<? super String> subscriber) { @Test public void shouldUnsubscribeFromUnderlyingSubscriptionOnDispose() { - final PublishProcessor<String> subject = PublishProcessor.create(); + final PublishProcessor<String> processor = PublishProcessor.create(); final TestScheduler scheduler = new TestScheduler(); - final TestSubscriber<String> observer = subject + final TestSubscriber<String> subscriber = processor .timeout(100, TimeUnit.MILLISECONDS, scheduler) .test(); - assertTrue(subject.hasSubscribers()); + assertTrue(processor.hasSubscribers()); - observer.dispose(); + subscriber.dispose(); - assertFalse(subject.hasSubscribers()); + assertFalse(processor.hasSubscribers()); } @Test @@ -425,26 +417,20 @@ public void timedEmpty() { .assertResult(); } - @Test - public void newTimer() { - FlowableTimeoutTimed.NEW_TIMER.dispose(); - assertTrue(FlowableTimeoutTimed.NEW_TIMER.isDisposed()); - } - @Test public void badSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - - observer.onNext(1); - observer.onComplete(); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .timeout(1, TimeUnit.DAYS) @@ -463,14 +449,14 @@ public void badSourceOther() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - - observer.onNext(1); - observer.onComplete(); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .timeout(1, TimeUnit.DAYS, Flowable.just(3)) @@ -483,39 +469,124 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { } } - @Test public void timedTake() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps.timeout(1, TimeUnit.DAYS) + TestSubscriber<Integer> ts = pp.timeout(1, TimeUnit.DAYS) .take(1) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); - to.assertResult(1); + ts.assertResult(1); } @Test public void timedFallbackTake() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps.timeout(1, TimeUnit.DAYS, Flowable.just(2)) + TestSubscriber<Integer> ts = pp.timeout(1, TimeUnit.DAYS, Flowable.just(2)) .take(1) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); + + pp.onNext(1); + + assertFalse(pp.hasSubscribers()); + + ts.assertResult(1); + } + + @Test + public void fallbackErrors() { + Flowable.never() + .timeout(1, TimeUnit.MILLISECONDS, Flowable.error(new TestException())) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + } + + @Test + public void onNextOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestScheduler sch = new TestScheduler(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp.timeout(1, TimeUnit.SECONDS, sch).test(); - ps.onNext(1); + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; - assertFalse(ps.hasSubscribers()); + TestHelper.race(r1, r2); - to.assertResult(1); + if (ts.valueCount() != 0) { + if (ts.errorCount() != 0) { + ts.assertFailure(TimeoutException.class, 1); + ts.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); + } else { + ts.assertValuesOnly(1); + } + } else { + ts.assertFailure(TimeoutException.class); + ts.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); + } + } } + @Test + public void onNextOnTimeoutRaceFallback() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestScheduler sch = new TestScheduler(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = pp.timeout(1, TimeUnit.SECONDS, sch, Flowable.just(2)).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; + + TestHelper.race(r1, r2); + + if (ts.isTerminated()) { + int c = ts.valueCount(); + if (c == 1) { + int v = ts.values().get(0); + assertTrue("" + v, v == 1 || v == 2); + } else { + ts.assertResult(1, 2); + } + } else { + ts.assertValuesOnly(1); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java index 77a5112305..d1ba19d32b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java @@ -14,12 +14,11 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import org.junit.Test; import org.mockito.InOrder; @@ -28,12 +27,13 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.PublishProcessor; +import io.reactivex.processors.*; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -52,22 +52,22 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.timeout(timeout, timeoutFunc, other).subscribe(o); + source.timeout(timeout, timeoutFunc, other).subscribe(subscriber); source.onNext(1); source.onNext(2); source.onNext(3); timeout.onNext(1); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o).onNext(3); - inOrder.verify(o).onNext(100); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onNext(3); + inOrder.verify(subscriber).onNext(100); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -85,16 +85,16 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.timeout(timeout, timeoutFunc, other).subscribe(o); + source.timeout(timeout, timeoutFunc, other).subscribe(subscriber); timeout.onNext(1); - inOrder.verify(o).onNext(100); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(100); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -119,13 +119,13 @@ public Flowable<Integer> call() { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.timeout(Flowable.defer(firstTimeoutFunc), timeoutFunc, other).subscribe(o); + source.timeout(Flowable.defer(firstTimeoutFunc), timeoutFunc, other).subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @@ -143,16 +143,16 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.timeout(timeout, timeoutFunc, other).subscribe(o); + source.timeout(timeout, timeoutFunc, other).subscribe(subscriber); source.onNext(1); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } @@ -170,13 +170,13 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.timeout(Flowable.<Integer> error(new TestException()), timeoutFunc, other).subscribe(o); + source.timeout(Flowable.<Integer> error(new TestException()), timeoutFunc, other).subscribe(subscriber); - verify(o).onError(any(TestException.class)); - verify(o, never()).onNext(any()); - verify(o, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(any()); + verify(subscriber, never()).onComplete(); } @@ -194,16 +194,16 @@ public Flowable<Integer> apply(Integer t1) { Flowable<Integer> other = Flowable.fromIterable(Arrays.asList(100)); - Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.timeout(timeout, timeoutFunc, other).subscribe(o); + source.timeout(timeout, timeoutFunc, other).subscribe(subscriber); source.onNext(1); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onError(any(TestException.class)); - verify(o, never()).onComplete(); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); } @@ -219,13 +219,13 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - source.timeout(timeout, timeoutFunc).subscribe(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + source.timeout(timeout, timeoutFunc).subscribe(subscriber); timeout.onNext(1); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onError(isA(TimeoutException.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onError(isA(TimeoutException.class)); inOrder.verifyNoMoreInteractions(); } @@ -241,15 +241,15 @@ public Flowable<Integer> apply(Integer t1) { } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); - source.timeout(PublishProcessor.create(), timeoutFunc).subscribe(o); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + source.timeout(PublishProcessor.create(), timeoutFunc).subscribe(subscriber); source.onNext(1); timeout.onNext(1); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onError(isA(TimeoutException.class)); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onError(isA(TimeoutException.class)); inOrder.verifyNoMoreInteractions(); } @@ -307,7 +307,7 @@ public void subscribe(Subscriber<? super Integer> subscriber) { } }; - final Subscriber<Integer> o = TestHelper.mockSubscriber(); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); doAnswer(new Answer<Void>() { @Override @@ -316,7 +316,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable { return null; } - }).when(o).onNext(2); + }).when(subscriber).onNext(2); doAnswer(new Answer<Void>() { @Override @@ -325,9 +325,9 @@ public Void answer(InvocationOnMock invocation) throws Throwable { return null; } - }).when(o).onComplete(); + }).when(subscriber).onComplete(); - final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(o); + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(subscriber); new Thread(new Runnable() { @@ -362,12 +362,12 @@ public void run() { assertFalse("CoundDownLatch timeout", latchTimeout.get()); - InOrder inOrder = inOrder(o); - inOrder.verify(o).onSubscribe((Subscription)notNull()); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onNext(2); - inOrder.verify(o, never()).onNext(3); - inOrder.verify(o).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber).onSubscribe((Subscription)notNull()); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber, never()).onNext(3); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -382,15 +382,15 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.timeout(Functions.justFunction(Flowable.never())); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.timeout(Functions.justFunction(Flowable.never())); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Object> o) throws Exception { - return o.timeout(Functions.justFunction(Flowable.never()), Flowable.never()); + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.timeout(Functions.justFunction(Flowable.never()), Flowable.never()); } }); } @@ -413,39 +413,39 @@ public void error() { @Test public void emptyInner() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .timeout(Functions.justFunction(Flowable.empty())) .test(); - ps.onNext(1); + pp.onNext(1); - to.assertFailure(TimeoutException.class, 1); + ts.assertFailure(TimeoutException.class, 1); } @Test public void badInnerSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .timeout(Functions.justFunction(new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onNext(2); - observer.onError(new TestException("Second")); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onNext(2); + subscriber.onError(new TestException("Second")); + subscriber.onComplete(); } })) .test(); - ps.onNext(1); + pp.onNext(1); - to.assertFailureAndMessage(TestException.class, "First", 1); + ts.assertFailureAndMessage(TestException.class, "First", 1); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); } finally { @@ -457,24 +457,24 @@ protected void subscribeActual(Subscriber<? super Integer> observer) { public void badInnerSourceOther() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .timeout(Functions.justFunction(new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onNext(2); - observer.onError(new TestException("Second")); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onNext(2); + subscriber.onError(new TestException("Second")); + subscriber.onComplete(); } }), Flowable.just(2)) .test(); - ps.onNext(1); + pp.onNext(1); - to.assertFailureAndMessage(TestException.class, "First", 1); + ts.assertFailureAndMessage(TestException.class, "First", 1); TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second"); } finally { @@ -492,57 +492,404 @@ public void withOtherMainError() { @Test public void badSourceTimeout() { - new Flowable<Integer>() { - @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new TestException("First")); - observer.onNext(3); - observer.onComplete(); - observer.onError(new TestException("Second")); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new TestException("First")); + subscriber.onNext(3); + subscriber.onComplete(); + subscriber.onError(new TestException("Second")); + } } + .timeout(Functions.justFunction(Flowable.never()), Flowable.<Integer>never()) + .take(1) + .test() + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "First"); + TestHelper.assertUndeliverable(errors, 1, TestException.class, "Second"); + } finally { + RxJavaPlugins.reset(); } - .timeout(Functions.justFunction(Flowable.never()), Flowable.<Integer>never()) - .take(1) - .test() - .assertResult(1); } @Test public void selectorTake() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .timeout(Functions.justFunction(Flowable.never())) .take(1) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); - ps.onNext(1); + pp.onNext(1); - assertFalse(ps.hasSubscribers()); + assertFalse(pp.hasSubscribers()); - to.assertResult(1); + ts.assertResult(1); } @Test public void selectorFallbackTake() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> to = ps + TestSubscriber<Integer> ts = pp .timeout(Functions.justFunction(Flowable.never()), Flowable.just(2)) .take(1) .test(); - assertTrue(ps.hasSubscribers()); + assertTrue(pp.hasSubscribers()); + + pp.onNext(1); + + assertFalse(pp.hasSubscribers()); + + ts.assertResult(1); + } + + @Test + public void lateOnTimeoutError() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final Subscriber<?>[] sub = { null, null }; + + final Flowable<Integer> pp2 = new Flowable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + sub[count++] = s; + } + }; + + TestSubscriber<Integer> ts = pp.timeout(Functions.justFunction(pp2)).test(); + + pp.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + final Throwable ex = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onError(ex); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void lateOnTimeoutFallbackRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final Subscriber<?>[] sub = { null, null }; + + final Flowable<Integer> pp2 = new Flowable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + assertFalse(((Disposable)s).isDisposed()); + s.onSubscribe(new BooleanSubscription()); + sub[count++] = s; + } + }; + + TestSubscriber<Integer> ts = pp.timeout(Functions.justFunction(pp2), Flowable.<Integer>never()).test(); + + pp.onNext(0); - ps.onNext(1); + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; - assertFalse(ps.hasSubscribers()); + final Throwable ex = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onError(ex); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onErrorOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); - to.assertResult(1); + final Subscriber<?>[] sub = { null, null }; + + final Flowable<Integer> pp2 = new Flowable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + assertFalse(((Disposable)s).isDisposed()); + s.onSubscribe(new BooleanSubscription()); + sub[count++] = s; + } + }; + + TestSubscriber<Integer> ts = pp.timeout(Functions.justFunction(pp2)).test(); + + pp.onNext(0); + + final Throwable ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onCompleteOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final Subscriber<?>[] sub = { null, null }; + + final Flowable<Integer> pp2 = new Flowable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + assertFalse(((Disposable)s).isDisposed()); + s.onSubscribe(new BooleanSubscription()); + sub[count++] = s; + } + }; + + TestSubscriber<Integer> ts = pp.timeout(Functions.justFunction(pp2)).test(); + + pp.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onCompleteOnTimeoutRaceFallback() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final Subscriber<?>[] sub = { null, null }; + + final Flowable<Integer> pp2 = new Flowable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + assertFalse(((Disposable)s).isDisposed()); + s.onSubscribe(new BooleanSubscription()); + sub[count++] = s; + } + }; + + TestSubscriber<Integer> ts = pp.timeout(Functions.justFunction(pp2), Flowable.<Integer>never()).test(); + + pp.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void disposedUpfront() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Object> timeoutAndFallback = Flowable.never().doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + counter.incrementAndGet(); + } + }); + + pp + .timeout(timeoutAndFallback, Functions.justFunction(timeoutAndFallback)) + .test(1, true) + .assertEmpty(); + + assertEquals(0, counter.get()); + } + + @Test + public void disposedUpfrontFallback() { + PublishProcessor<Object> pp = PublishProcessor.create(); + final AtomicInteger counter = new AtomicInteger(); + + Flowable<Object> timeoutAndFallback = Flowable.never().doOnSubscribe(new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + counter.incrementAndGet(); + } + }); + + pp + .timeout(timeoutAndFallback, Functions.justFunction(timeoutAndFallback), timeoutAndFallback) + .test(1, true) + .assertEmpty(); + + assertEquals(0, counter.get()); + } + + @Test + public void timeoutConsumerDoubleOnSubscribe() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor.createDefault(1) + .timeout(Functions.justFunction(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + BooleanSubscription bs1 = new BooleanSubscription(); + s.onSubscribe(bs1); + + BooleanSubscription bs2 = new BooleanSubscription(); + s.onSubscribe(bs2); + + assertFalse(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + + s.onComplete(); + } + })) + .test() + .assertFailure(TimeoutException.class, 1); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java index 16052e328d..b02fea95b3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -35,29 +34,29 @@ public class FlowableTimerTest { @Mock - Subscriber<Object> observer; + Subscriber<Object> subscriber; @Mock - Subscriber<Long> observer2; + Subscriber<Long> subscriber2; TestScheduler scheduler; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); - observer2 = TestHelper.mockSubscriber(); + subscriber2 = TestHelper.mockSubscriber(); scheduler = new TestScheduler(); } @Test public void testTimerOnce() { - Flowable.timer(100, TimeUnit.MILLISECONDS, scheduler).subscribe(observer); + Flowable.timer(100, TimeUnit.MILLISECONDS, scheduler).subscribe(subscriber); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - verify(observer, times(1)).onNext(0L); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(0L); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -86,6 +85,7 @@ public void testTimerPeriodically() { ts.assertNotComplete(); ts.assertNoErrors(); } + @Test public void testInterval() { Flowable<Long> w = Flowable.interval(1, TimeUnit.SECONDS, scheduler); @@ -226,6 +226,7 @@ public void testWithMultipleStaggeredSubscribersAndPublish() { ts2.assertNoErrors(); ts2.assertNotComplete(); } + @Test public void testOnceObserverThrows() { Flowable<Long> source = Flowable.timer(100, TimeUnit.MILLISECONDS, scheduler); @@ -239,26 +240,27 @@ public void onNext(Long t) { @Override public void onError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - observer.onComplete(); + subscriber.onComplete(); } }); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - verify(observer).onError(any(TestException.class)); - verify(observer, never()).onNext(anyLong()); - verify(observer, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); + verify(subscriber, never()).onNext(anyLong()); + verify(subscriber, never()).onComplete(); } + @Test public void testPeriodicObserverThrows() { Flowable<Long> source = Flowable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); source.safeSubscribe(new DefaultSubscriber<Long>() { @@ -267,26 +269,26 @@ public void onNext(Long t) { if (t > 0) { throw new TestException(); } - observer.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - observer.onComplete(); + subscriber.onComplete(); } }); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - inOrder.verify(observer).onNext(0L); - inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verify(subscriber).onNext(0L); + inOrder.verify(subscriber).onError(any(TestException.class)); inOrder.verifyNoMoreInteractions(); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onComplete(); } @Test @@ -304,7 +306,7 @@ public void backpressureNotReady() { @Test public void timerCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Long> ts = new TestSubscriber<Long>(); final TestScheduler scheduler = new TestScheduler(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimestampTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimestampTest.java index 1172245f9c..295f4d3a2b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimestampTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimestampTest.java @@ -28,11 +28,11 @@ import io.reactivex.schedulers.*; public class FlowableTimestampTest { - Subscriber<Object> observer; + Subscriber<Object> subscriber; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } @Test @@ -41,7 +41,7 @@ public void timestampWithScheduler() { PublishProcessor<Integer> source = PublishProcessor.create(); Flowable<Timed<Integer>> m = source.timestamp(scheduler); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(1); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); @@ -49,14 +49,14 @@ public void timestampWithScheduler() { scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); source.onNext(3); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(1, 0, TimeUnit.MILLISECONDS)); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(2, 100, TimeUnit.MILLISECONDS)); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(3, 200, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(1, 0, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(2, 100, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(3, 200, TimeUnit.MILLISECONDS)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test @@ -65,7 +65,7 @@ public void timestampWithScheduler2() { PublishProcessor<Integer> source = PublishProcessor.create(); Flowable<Timed<Integer>> m = source.timestamp(scheduler); - m.subscribe(observer); + m.subscribe(subscriber); source.onNext(1); source.onNext(2); @@ -73,14 +73,14 @@ public void timestampWithScheduler2() { scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); source.onNext(3); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(1, 0, TimeUnit.MILLISECONDS)); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(2, 0, TimeUnit.MILLISECONDS)); - inOrder.verify(observer, times(1)).onNext(new Timed<Integer>(3, 200, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(1, 0, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(2, 0, TimeUnit.MILLISECONDS)); + inOrder.verify(subscriber, times(1)).onNext(new Timed<Integer>(3, 200, TimeUnit.MILLISECONDS)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToFutureTest.java index afe111c919..2fb5920d57 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToFutureTest.java @@ -34,17 +34,17 @@ public void testSuccess() throws Exception { Object value = new Object(); when(future.get()).thenReturn(value); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); Flowable.fromFuture(future).subscribe(ts); ts.dispose(); - verify(o, times(1)).onNext(value); - verify(o, times(1)).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onNext(value); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); verify(future, never()).cancel(anyBoolean()); } @@ -55,18 +55,18 @@ public void testSuccessOperatesOnSuppliedScheduler() throws Exception { Object value = new Object(); when(future.get()).thenReturn(value); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); TestScheduler scheduler = new TestScheduler(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); Flowable.fromFuture(future, scheduler).subscribe(ts); - verify(o, never()).onNext(value); + verify(subscriber, never()).onNext(value); scheduler.triggerActions(); - verify(o, times(1)).onNext(value); + verify(subscriber, times(1)).onNext(value); } @Test @@ -76,17 +76,17 @@ public void testFailure() throws Exception { RuntimeException e = new RuntimeException(); when(future.get()).thenThrow(e); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); Flowable.fromFuture(future).subscribe(ts); ts.dispose(); - verify(o, never()).onNext(null); - verify(o, never()).onComplete(); - verify(o, times(1)).onError(e); + verify(subscriber, never()).onNext(null); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onError(e); verify(future, never()).cancel(anyBoolean()); } @@ -97,9 +97,9 @@ public void testCancelledBeforeSubscribe() throws Exception { CancellationException e = new CancellationException("unit test synthetic cancellation"); when(future.get()).thenThrow(e); - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); ts.dispose(); Flowable.fromFuture(future).subscribe(ts); @@ -143,9 +143,9 @@ public Object get(long timeout, TimeUnit unit) throws InterruptedException, Exec } }; - Subscriber<Object> o = TestHelper.mockSubscriber(); + Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - TestSubscriber<Object> ts = new TestSubscriber<Object>(o); + TestSubscriber<Object> ts = new TestSubscriber<Object>(subscriber); Flowable<Object> futureObservable = Flowable.fromFuture(future); futureObservable.subscribeOn(Schedulers.computation()).subscribe(ts); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java index 2bb37c2f18..08dc9c244d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -25,6 +24,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; @@ -35,68 +35,72 @@ public class FlowableToListTest { @Test public void testListFlowable() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Flowable<List<String>> observable = w.toList().toFlowable(); + Flowable<List<String>> flowable = w.toList().toFlowable(); - Subscriber<List<String>> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext(Arrays.asList("one", "two", "three")); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<List<String>> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(Arrays.asList("one", "two", "three")); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testListViaFlowableFlowable() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Flowable<List<String>> observable = w.toList().toFlowable(); + Flowable<List<String>> flowable = w.toList().toFlowable(); - Subscriber<List<String>> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext(Arrays.asList("one", "two", "three")); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<List<String>> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(Arrays.asList("one", "two", "three")); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testListMultipleSubscribersFlowable() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Flowable<List<String>> observable = w.toList().toFlowable(); + Flowable<List<String>> flowable = w.toList().toFlowable(); - Subscriber<List<String>> o1 = TestHelper.mockSubscriber(); - observable.subscribe(o1); + Subscriber<List<String>> subscriber1 = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber1); - Subscriber<List<String>> o2 = TestHelper.mockSubscriber(); - observable.subscribe(o2); + Subscriber<List<String>> subscriber2 = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber2); List<String> expected = Arrays.asList("one", "two", "three"); - verify(o1, times(1)).onNext(expected); - verify(o1, Mockito.never()).onError(any(Throwable.class)); - verify(o1, times(1)).onComplete(); + verify(subscriber1, times(1)).onNext(expected); + verify(subscriber1, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber1, times(1)).onComplete(); - verify(o2, times(1)).onNext(expected); - verify(o2, Mockito.never()).onError(any(Throwable.class)); - verify(o2, times(1)).onComplete(); + verify(subscriber2, times(1)).onNext(expected); + verify(subscriber2, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber2, times(1)).onComplete(); } @Test @Ignore("Null values are not allowed") public void testListWithNullValueFlowable() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", null, "three")); - Flowable<List<String>> observable = w.toList().toFlowable(); + Flowable<List<String>> flowable = w.toList().toFlowable(); - Subscriber<List<String>> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext(Arrays.asList("one", null, "three")); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<List<String>> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(Arrays.asList("one", null, "three")); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testListWithBlockingFirstFlowable() { - Flowable<String> o = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - List<String> actual = o.toList().toFlowable().blockingFirst(); + Flowable<String> f = Flowable.fromIterable(Arrays.asList("one", "two", "three")); + List<String> actual = f.toList().toFlowable().blockingFirst(); Assert.assertEquals(Arrays.asList("one", "two", "three"), actual); } + @Test public void testBackpressureHonoredFlowable() { Flowable<List<Integer>> w = Flowable.just(1, 2, 3, 4, 5).toList().toFlowable(); @@ -120,6 +124,7 @@ public void testBackpressureHonoredFlowable() { ts.assertNoErrors(); ts.assertComplete(); } + @Test(timeout = 2000) @Ignore("PublishProcessor no longer emits without requests so this test fails due to the race of onComplete and request") public void testAsyncRequestedFlowable() { @@ -169,10 +174,10 @@ public void capacityHintFlowable() { @Test public void testList() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", "two", "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -180,10 +185,10 @@ public void testList() { @Test public void testListViaFlowable() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", "two", "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -191,13 +196,13 @@ public void testListViaFlowable() { @Test public void testListMultipleSubscribers() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> o1 = TestHelper.mockSingleObserver(); - observable.subscribe(o1); + single.subscribe(o1); SingleObserver<List<String>> o2 = TestHelper.mockSingleObserver(); - observable.subscribe(o2); + single.subscribe(o2); List<String> expected = Arrays.asList("one", "two", "three"); @@ -212,44 +217,46 @@ public void testListMultipleSubscribers() { @Ignore("Null values are not allowed") public void testListWithNullValue() { Flowable<String> w = Flowable.fromIterable(Arrays.asList("one", null, "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", null, "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @Test public void testListWithBlockingFirst() { - Flowable<String> o = Flowable.fromIterable(Arrays.asList("one", "two", "three")); - List<String> actual = o.toList().blockingGet(); + Flowable<String> f = Flowable.fromIterable(Arrays.asList("one", "two", "three")); + List<String> actual = f.toList().blockingGet(); Assert.assertEquals(Arrays.asList("one", "two", "three"), actual); } + @Test @Ignore("Single doesn't do backpressure") public void testBackpressureHonored() { Single<List<Integer>> w = Flowable.just(1, 2, 3, 4, 5).toList(); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); - w.subscribe(ts); + w.subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); // ts.request(1); - ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + to.assertNoErrors(); + to.assertComplete(); // ts.request(1); - ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + to.assertNoErrors(); + to.assertComplete(); } + @Test(timeout = 2000) @Ignore("PublishProcessor no longer emits without requests so this test fails due to the race of onComplete and request") public void testAsyncRequested() { @@ -263,8 +270,8 @@ public void testAsyncRequested() { Single<List<Integer>> sorted = source.toList(); final CyclicBarrier cb = new CyclicBarrier(2); - final TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); - sorted.subscribe(ts); + final TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + sorted.subscribe(to); w.schedule(new Runnable() { @Override @@ -276,10 +283,10 @@ public void run() { source.onNext(1); await(cb); source.onComplete(); - ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValue(Arrays.asList(1)); + to.awaitTerminalEvent(1, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValue(Arrays.asList(1)); } } finally { w.dispose(); @@ -392,9 +399,9 @@ public Collection<Integer> call() throws Exception { @Test public void onNextCancelRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestObserver<List<Integer>> ts = pp.toList().test(); + final TestObserver<List<Integer>> to = pp.toList().test(); Runnable r1 = new Runnable() { @Override @@ -405,7 +412,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; @@ -415,7 +422,7 @@ public void run() { @Test public void onNextCancelRaceFlowable() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<List<Integer>> ts = pp.toList().toFlowable().test(); @@ -439,7 +446,7 @@ public void run() { @Test public void onCompleteCancelRaceFlowable() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<List<Integer>> ts = pp.toList().toFlowable().test(); @@ -466,4 +473,22 @@ public void run() { } } } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<List<Object>>>() { + @Override + public Flowable<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.toList().toFlowable(); + } + }); + TestHelper.checkDoubleOnSubscribeFlowableToSingle(new Function<Flowable<Object>, Single<List<Object>>>() { + @Override + public Single<List<Object>> apply(Flowable<Object> f) + throws Exception { + return f.toList(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java index 8723a291bc..296f68eef6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMapTest.java @@ -25,12 +25,12 @@ import io.reactivex.functions.Function; public class FlowableToMapTest { - Subscriber<Object> objectObserver; + Subscriber<Object> objectSubscriber; SingleObserver<Object> singleObserver; @Before public void before() { - objectObserver = TestHelper.mockSubscriber(); + objectSubscriber = TestHelper.mockSubscriber(); singleObserver = TestHelper.mockSingleObserver(); } @@ -59,11 +59,11 @@ public void testToMapFlowable() { expected.put(3, "ccc"); expected.put(4, "dddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -78,11 +78,11 @@ public void testToMapWithValueSelectorFlowable() { expected.put(3, "cccccc"); expected.put(4, "dddddddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -106,11 +106,11 @@ public Integer apply(String t1) { expected.put(3, "ccc"); expected.put(4, "dddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); - verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); } @@ -136,11 +136,11 @@ public String apply(String t1) { expected.put(3, "cccccc"); expected.put(4, "dddddddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); - verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); } @@ -181,11 +181,11 @@ public String apply(String v) { expected.put(3, "ccc"); expected.put(4, "dddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -217,14 +217,13 @@ public String apply(String v) { expected.put(3, "ccc"); expected.put(4, "dddd"); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); - verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); } - @Test public void testToMap() { Flowable<String> source = Flowable.just("a", "bb", "ccc", "dddd"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java index af642121fb..bb77dab822 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToMultimapTest.java @@ -25,13 +25,13 @@ import io.reactivex.functions.Function; public class FlowableToMultimapTest { - Subscriber<Object> objectObserver; + Subscriber<Object> objectSubscriber; SingleObserver<Object> singleObserver; @Before public void before() { - objectObserver = TestHelper.mockSubscriber(); + objectSubscriber = TestHelper.mockSubscriber(); singleObserver = TestHelper.mockSingleObserver(); } @@ -58,11 +58,11 @@ public void testToMultimapFlowable() { expected.put(1, Arrays.asList("a", "b")); expected.put(2, Arrays.asList("cc", "dd")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -75,11 +75,11 @@ public void testToMultimapWithValueSelectorFlowable() { expected.put(1, Arrays.asList("aa", "bb")); expected.put(2, Arrays.asList("cccc", "dddd")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -121,11 +121,11 @@ public Collection<String> apply(Integer e) { expected.put(2, Arrays.asList("cc", "dd")); expected.put(3, Arrays.asList("eee", "fff")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -163,11 +163,11 @@ public Map<Integer, Collection<String>> call() { expected.put(2, Arrays.asList("cc", "dd")); expected.put(3, new HashSet<String>(Arrays.asList("eee"))); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, never()).onError(any(Throwable.class)); - verify(objectObserver, times(1)).onNext(expected); - verify(objectObserver, times(1)).onComplete(); + verify(objectSubscriber, never()).onError(any(Throwable.class)); + verify(objectSubscriber, times(1)).onNext(expected); + verify(objectSubscriber, times(1)).onComplete(); } @Test @@ -190,11 +190,11 @@ public Integer apply(String t1) { expected.put(1, Arrays.asList("a", "b")); expected.put(2, Arrays.asList("cc", "dd")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, times(1)).onError(any(Throwable.class)); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); } @Test @@ -217,11 +217,11 @@ public String apply(String t1) { expected.put(1, Arrays.asList("aa", "bb")); expected.put(2, Arrays.asList("cccc", "dddd")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, times(1)).onError(any(Throwable.class)); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); } @Test @@ -247,11 +247,11 @@ public String apply(String v) { expected.put(2, Arrays.asList("cc", "dd")); expected.put(3, Arrays.asList("eee", "fff")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, times(1)).onError(any(Throwable.class)); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); } @Test @@ -289,14 +289,13 @@ public Map<Integer, Collection<String>> call() { expected.put(2, Arrays.asList("cc", "dd")); expected.put(3, Collections.singleton("eee")); - mapped.subscribe(objectObserver); + mapped.subscribe(objectSubscriber); - verify(objectObserver, times(1)).onError(any(Throwable.class)); - verify(objectObserver, never()).onNext(expected); - verify(objectObserver, never()).onComplete(); + verify(objectSubscriber, times(1)).onError(any(Throwable.class)); + verify(objectSubscriber, never()).onNext(expected); + verify(objectSubscriber, never()).onComplete(); } - @Test public void testToMultimap() { Flowable<String> source = Flowable.just("a", "b", "cc", "dd"); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java index 8a840f4b30..173da4830d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToSortedListTest.java @@ -34,19 +34,20 @@ public class FlowableToSortedListTest { @Test public void testSortedListFlowable() { Flowable<Integer> w = Flowable.just(1, 3, 2, 5, 4); - Flowable<List<Integer>> observable = w.toSortedList().toFlowable(); + Flowable<List<Integer>> flowable = w.toSortedList().toFlowable(); - Subscriber<List<Integer>> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext(Arrays.asList(1, 2, 3, 4, 5)); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<List<Integer>> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(Arrays.asList(1, 2, 3, 4, 5)); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSortedListWithCustomFunctionFlowable() { Flowable<Integer> w = Flowable.just(1, 3, 2, 5, 4); - Flowable<List<Integer>> observable = w.toSortedList(new Comparator<Integer>() { + Flowable<List<Integer>> flowable = w.toSortedList(new Comparator<Integer>() { @Override public int compare(Integer t1, Integer t2) { @@ -55,18 +56,20 @@ public int compare(Integer t1, Integer t2) { }).toFlowable(); - Subscriber<List<Integer>> observer = TestHelper.mockSubscriber(); - observable.subscribe(observer); - verify(observer, times(1)).onNext(Arrays.asList(5, 4, 3, 2, 1)); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + Subscriber<List<Integer>> subscriber = TestHelper.mockSubscriber(); + flowable.subscribe(subscriber); + + verify(subscriber, times(1)).onNext(Arrays.asList(5, 4, 3, 2, 1)); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testWithFollowingFirstFlowable() { - Flowable<Integer> o = Flowable.just(1, 3, 2, 5, 4); - assertEquals(Arrays.asList(1, 2, 3, 4, 5), o.toSortedList().toFlowable().blockingFirst()); + Flowable<Integer> f = Flowable.just(1, 3, 2, 5, 4); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), f.toSortedList().toFlowable().blockingFirst()); } + @Test public void testBackpressureHonoredFlowable() { Flowable<List<Integer>> w = Flowable.just(1, 3, 2, 5, 4).toSortedList().toFlowable(); @@ -169,10 +172,10 @@ public int compare(Integer a, Integer b) { @Test public void testSortedList() { Flowable<Integer> w = Flowable.just(1, 3, 2, 5, 4); - Single<List<Integer>> observable = w.toSortedList(); + Single<List<Integer>> single = w.toSortedList(); SingleObserver<List<Integer>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList(1, 2, 3, 4, 5)); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -180,7 +183,7 @@ public void testSortedList() { @Test public void testSortedListWithCustomFunction() { Flowable<Integer> w = Flowable.just(1, 3, 2, 5, 4); - Single<List<Integer>> observable = w.toSortedList(new Comparator<Integer>() { + Single<List<Integer>> single = w.toSortedList(new Comparator<Integer>() { @Override public int compare(Integer t1, Integer t2) { @@ -190,39 +193,40 @@ public int compare(Integer t1, Integer t2) { }); SingleObserver<List<Integer>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList(5, 4, 3, 2, 1)); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @Test public void testWithFollowingFirst() { - Flowable<Integer> o = Flowable.just(1, 3, 2, 5, 4); - assertEquals(Arrays.asList(1, 2, 3, 4, 5), o.toSortedList().blockingGet()); + Flowable<Integer> f = Flowable.just(1, 3, 2, 5, 4); + assertEquals(Arrays.asList(1, 2, 3, 4, 5), f.toSortedList().blockingGet()); } + @Test @Ignore("Single doesn't do backpressure") public void testBackpressureHonored() { Single<List<Integer>> w = Flowable.just(1, 3, 2, 5, 4).toSortedList(); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); - w.subscribe(ts); + w.subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); // ts.request(1); - ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + to.assertNoErrors(); + to.assertComplete(); // ts.request(1); - ts.assertValue(Arrays.asList(1, 2, 3, 4, 5)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 2, 3, 4, 5)); + to.assertNoErrors(); + to.assertComplete(); } @Test(timeout = 2000) @@ -238,8 +242,8 @@ public void testAsyncRequested() { Single<List<Integer>> sorted = source.toSortedList(); final CyclicBarrier cb = new CyclicBarrier(2); - final TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); - sorted.subscribe(ts); + final TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + sorted.subscribe(to); w.schedule(new Runnable() { @Override public void run() { @@ -250,10 +254,10 @@ public void run() { source.onNext(1); await(cb); source.onComplete(); - ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValue(Arrays.asList(1)); + to.awaitTerminalEvent(1, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValue(Arrays.asList(1)); } } finally { w.dispose(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java index cc9218a1d8..36112023bb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java @@ -35,7 +35,7 @@ public class FlowableUnsubscribeOnTest { @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>(); @@ -47,13 +47,16 @@ public void subscribe(Subscriber<? super Integer> t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - w.subscribeOn(UI_EVENT_LOOP).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + w.subscribeOn(uiEventLoop).observeOn(Schedulers.computation()) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(ts); @@ -70,18 +73,18 @@ public void subscribe(Subscriber<? super Integer> t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread == UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); ts.assertValues(1, 2); ts.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>(); @@ -93,17 +96,20 @@ public void subscribe(Subscriber<? super Integer> t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); - TestSubscriber<Integer> observer = new TestSubscriber<Integer>(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); w.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + .unsubscribeOn(uiEventLoop) .take(2) - .subscribe(observer); + .subscribe(ts); - observer.awaitTerminalEvent(1, TimeUnit.SECONDS); + ts.awaitTerminalEvent(1, TimeUnit.SECONDS); Thread unsubscribeThread = subscription.getThread(); @@ -114,15 +120,15 @@ public void subscribe(Subscriber<? super Integer> t1) { assertNotSame(Thread.currentThread(), subscribeThread.get()); // True for Schedulers.newThread() - System.out.println("UI Thread: " + UI_EVENT_LOOP.getThread()); + System.out.println("UI Thread: " + uiEventLoop.getThread()); System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertSame(unsubscribeThread, UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); - observer.assertValues(1, 2); - observer.assertTerminated(); + ts.assertValues(1, 2); + ts.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } @@ -250,12 +256,12 @@ public void signalAfterDispose() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .unsubscribeOn(Schedulers.single()) diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java index ebb7a9722b..cfc8ab20b1 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUsingTest.java @@ -29,6 +29,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subscribers.TestSubscriber; @@ -52,8 +53,8 @@ public void accept(Resource r) { private final Consumer<Disposable> disposeSubscription = new Consumer<Disposable>() { @Override - public void accept(Disposable s) { - s.dispose(); + public void accept(Disposable d) { + d.dispose(); } }; @@ -86,16 +87,16 @@ public Flowable<String> apply(Resource res) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), disposeEagerly); - observable.subscribe(observer); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("Hello"); - inOrder.verify(observer, times(1)).onNext("world!"); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext("Hello"); + inOrder.verify(subscriber, times(1)).onNext("world!"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); // The resouce should be closed @@ -146,22 +147,22 @@ public Flowable<String> apply(Resource res) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), disposeEagerly); - observable.subscribe(observer); - observable.subscribe(observer); + flowable.subscribe(subscriber); + flowable.subscribe(subscriber); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, times(1)).onNext("Hello"); - inOrder.verify(observer, times(1)).onNext("world!"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("Hello"); + inOrder.verify(subscriber, times(1)).onNext("world!"); + inOrder.verify(subscriber, times(1)).onComplete(); - inOrder.verify(observer, times(1)).onNext("Hello"); - inOrder.verify(observer, times(1)).onNext("world!"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("Hello"); + inOrder.verify(subscriber, times(1)).onNext("world!"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -185,7 +186,7 @@ public Disposable call() { Function<Disposable, Flowable<Integer>> observableFactory = new Function<Disposable, Flowable<Integer>>() { @Override - public Flowable<Integer> apply(Disposable s) { + public Flowable<Integer> apply(Disposable d) { return Flowable.empty(); } }; @@ -290,14 +291,14 @@ public Flowable<String> apply(Resource resource) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), true) .doOnCancel(unsub) .doOnComplete(completion); - observable.safeSubscribe(observer); + flowable.safeSubscribe(subscriber); assertEquals(Arrays.asList("disposed", "completed"), events); @@ -317,21 +318,19 @@ public Flowable<String> apply(Resource resource) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), false) .doOnCancel(unsub) .doOnComplete(completion); - observable.safeSubscribe(observer); + flowable.safeSubscribe(subscriber); assertEquals(Arrays.asList("completed", "disposed"), events); } - - @Test public void testUsingDisposesEagerlyBeforeError() { final List<String> events = new ArrayList<String>(); @@ -347,14 +346,14 @@ public Flowable<String> apply(Resource resource) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), true) .doOnCancel(unsub) .doOnError(onError); - observable.safeSubscribe(observer); + flowable.safeSubscribe(subscriber); assertEquals(Arrays.asList("disposed", "error"), events); @@ -375,14 +374,14 @@ public Flowable<String> apply(Resource resource) { } }; - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable<String> observable = Flowable.using(resourceFactory, observableFactory, + Flowable<String> flowable = Flowable.using(resourceFactory, observableFactory, new DisposeAction(), false) .doOnCancel(unsub) .doOnError(onError); - observable.safeSubscribe(observer); + flowable.safeSubscribe(subscriber); assertEquals(Arrays.asList("error", "disposed"), events); } @@ -525,7 +524,7 @@ public Flowable<Object> apply(Object v) throws Exception { @Test public void supplierDisposerCrash() { - TestSubscriber<Object> to = Flowable.using(new Callable<Object>() { + TestSubscriber<Object> ts = Flowable.using(new Callable<Object>() { @Override public Object call() throws Exception { return 1; @@ -544,7 +543,7 @@ public void accept(Object e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "First"); TestHelper.assertError(errors, 1, TestException.class, "Second"); @@ -552,7 +551,7 @@ public void accept(Object e) throws Exception { @Test public void eagerOnErrorDisposerCrash() { - TestSubscriber<Object> to = Flowable.using(new Callable<Object>() { + TestSubscriber<Object> ts = Flowable.using(new Callable<Object>() { @Override public Object call() throws Exception { return 1; @@ -571,7 +570,7 @@ public void accept(Object e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "First"); TestHelper.assertError(errors, 1, TestException.class, "Second"); @@ -637,4 +636,45 @@ public void sourceSupplierReturnsNull() { .assertFailureAndMessage(NullPointerException.class, "The sourceSupplier returned a null Publisher") ; } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return Flowable.using(Functions.justCallable(1), Functions.justFunction(f), Functions.emptyConsumer()); + } + }); + } + + @Test + public void eagerDisposedOnComplete() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.using(Functions.justCallable(1), Functions.justFunction(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ts.cancel(); + subscriber.onComplete(); + } + }), Functions.emptyConsumer(), true) + .subscribe(ts); + } + + @Test + public void eagerDisposedOnError() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.using(Functions.justCallable(1), Functions.justFunction(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ts.cancel(); + subscriber.onError(new TestException()); + } + }), Functions.emptyConsumer(), true) + .subscribe(ts); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java index 3994d5dc31..ce321df789 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java @@ -14,20 +14,21 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.Test; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.subscribers.*; @@ -38,7 +39,7 @@ public void testWindowViaFlowableNormal1() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); @@ -53,12 +54,12 @@ public void onNext(Flowable<Integer> args) { @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }; @@ -73,8 +74,7 @@ public void onComplete() { } source.onComplete(); - - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); assertEquals(n / 3, values.size()); @@ -88,7 +88,7 @@ public void onComplete() { j += 3; } - verify(o).onComplete(); + verify(subscriber).onComplete(); } @Test @@ -96,7 +96,7 @@ public void testWindowViaFlowableBoundaryCompletes() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); @@ -111,12 +111,12 @@ public void onNext(Flowable<Integer> args) { @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }; @@ -143,8 +143,8 @@ public void onComplete() { j += 3; } - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -152,7 +152,7 @@ public void testWindowViaFlowableBoundaryThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); @@ -167,12 +167,12 @@ public void onNext(Flowable<Integer> args) { @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }; @@ -193,16 +193,16 @@ public void onComplete() { verify(mo).onNext(2); verify(mo).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } @Test - public void testWindowViaFlowableSourceThrows() { + public void testWindowViaFlowableThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); @@ -217,12 +217,12 @@ public void onNext(Flowable<Integer> args) { @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }; @@ -243,8 +243,8 @@ public void onComplete() { verify(mo).onNext(2); verify(mo).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o).onError(any(TestException.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber).onError(any(TestException.class)); } @Test @@ -324,6 +324,7 @@ public Flowable<Integer> call() { ts.assertNoErrors(); ts.assertValueCount(1); } + @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -343,8 +344,7 @@ public Flowable<Integer> call() { boundary.onComplete(); - // FIXME source still active because the open window - assertTrue(source.hasSubscribers()); + assertFalse(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); ts.assertComplete(); @@ -371,15 +371,19 @@ public Flowable<Integer> call() { ts.dispose(); - // FIXME source has subscribers because the open window assertTrue(source.hasSubscribers()); - // FIXME boundary has subscribers because the open window - assertTrue(boundary.hasSubscribers()); + + assertFalse(boundary.hasSubscribers()); + + ts.values().get(0).test().cancel(); + + assertFalse(source.hasSubscribers()); ts.assertNotComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } + @Test public void testInnerBackpressure() { Flowable<Integer> source = Flowable.range(1, 10); @@ -468,13 +472,13 @@ public void boundaryDispose2() { @Test public void boundaryOnError() { - TestSubscriber<Object> to = Flowable.error(new TestException()) + TestSubscriber<Object> ts = Flowable.error(new TestException()) .window(Flowable.never()) .flatMap(Functions.<Flowable<Object>>identity(), true) .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class); } @@ -491,8 +495,8 @@ public void mainError() { public void innerBadSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return Flowable.just(1).window(o).flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { + public Object apply(Flowable<Integer> f) throws Exception { + return Flowable.just(1).window(f).flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; @@ -503,8 +507,18 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override - public Object apply(Flowable<Integer> o) throws Exception { - return Flowable.just(1).window(Functions.justCallable(o)).flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { + public Object apply(final Flowable<Integer> f) throws Exception { + return Flowable.just(1).window(new Callable<Publisher<Integer>>() { + int count; + @Override + public Publisher<Integer> call() throws Exception { + if (++count > 1) { + return Flowable.never(); + } + return f; + } + }) + .flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; @@ -518,7 +532,7 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { public void reentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -536,11 +550,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -549,7 +563,7 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { public void reentrantCallable() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -577,11 +591,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -590,8 +604,8 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.window(Flowable.never()).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { + public Object apply(Flowable<Object> f) throws Exception { + return f.window(Flowable.never()).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> v) throws Exception { return v; @@ -605,8 +619,8 @@ public Flowable<Object> apply(Flowable<Object> v) throws Exception { public void badSourceCallable() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.window(Functions.justCallable(Flowable.never())).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { + public Object apply(Flowable<Object> f) throws Exception { + return f.window(Functions.justCallable(Flowable.never())).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> v) throws Exception { return v; @@ -615,4 +629,739 @@ public Flowable<Object> apply(Flowable<Object> v) throws Exception { } }, false, 1, 1, 1); } + + @Test + public void boundaryError() { + BehaviorProcessor.createDefault(1) + .window(Functions.justCallable(Flowable.error(new TestException()))) + .test() + .assertValueCount(1) + .assertNotComplete() + .assertError(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void boundaryMissingBackpressure() { + BehaviorProcessor.createDefault(1) + .window(Functions.justCallable(Flowable.error(new TestException()))) + .test(0) + .assertFailure(MissingBackpressureException.class); + } + + @Test + public void boundaryCallableCrashOnCall2() { + BehaviorProcessor.createDefault(1) + .window(new Callable<Flowable<Integer>>() { + int calls; + @Override + public Flowable<Integer> call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return Flowable.just(1); + } + }) + .test() + .assertError(TestException.class) + .assertNotComplete(); + } + + @Test + public void boundarySecondMissingBackpressure() { + BehaviorProcessor.createDefault(1) + .window(Functions.justCallable(Flowable.just(1))) + .test(1) + .assertError(MissingBackpressureException.class) + .assertNotComplete(); + } + + @Test + public void oneWindow() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Flowable<Integer>> ts = BehaviorProcessor.createDefault(1) + .window(Functions.justCallable(pp)) + .take(1) + .test(); + + pp.onNext(1); + + ts + .assertValueCount(1) + .assertNoErrors() + .assertComplete(); + } + + @SuppressWarnings("unchecked") + @Test + public void boundaryDirectMissingBackpressure() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor.create() + .window(Flowable.error(new TestException())) + .test(0) + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @SuppressWarnings("unchecked") + @Test + public void boundaryDirectMissingBackpressureNoNullPointerException() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor.createDefault(1) + .window(Flowable.error(new TestException())) + .test(0) + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void boundaryDirectSecondMissingBackpressure() { + BehaviorProcessor.createDefault(1) + .window(Flowable.just(1)) + .test(1) + .assertError(MissingBackpressureException.class) + .assertNotComplete(); + } + + @Test + public void boundaryDirectDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Flowable<Object>>>() { + @Override + public Publisher<Flowable<Object>> apply(Flowable<Object> f) + throws Exception { + return f.window(Flowable.never()).takeLast(1); + } + }); + } + + @Test + public void upstreamDisposedWhenOutputsDisposed() { + PublishProcessor<Integer> source = PublishProcessor.create(); + PublishProcessor<Integer> boundary = PublishProcessor.create(); + + TestSubscriber<Integer> ts = source.window(boundary) + .take(1) + .flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply( + Flowable<Integer> w) throws Exception { + return w.take(1); + } + }) + .test(); + + source.onNext(1); + + assertFalse("source not disposed", source.hasSubscribers()); + assertFalse("boundary not disposed", boundary.hasSubscribers()); + + ts.assertResult(1); + } + + @Test + public void mainAndBoundaryBothError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = Flowable.error(new TestException("main")) + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }) + .test(); + + ts + .assertValueCount(1) + .assertError(TestException.class) + .assertErrorMessage("main") + .assertNotComplete(); + + ref.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void mainCompleteBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onComplete(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onError(ex); + } + }; + + TestHelper.race(r1, r2); + + ts + .assertValueCount(1) + .assertTerminated(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainNextBoundaryNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onNext(1); + } + }; + + TestHelper.race(r1, r2); + + ts + .assertValueCount(2) + .assertNotComplete() + .assertNoErrors(); + } + } + + @Test + public void takeOneAnotherBoundary() { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + }) + .test(); + + ts.assertValueCount(1) + .assertNotTerminated() + .cancel(); + + ref.get().onNext(1); + + ts.assertValueCount(1) + .assertNotTerminated(); + } + + @Test + public void disposeMainBoundaryCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + final TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + final AtomicInteger counter = new AtomicInteger(); + subscriber.onSubscribe(new Subscription() { + + @Override + public void cancel() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public void request(long n) { + } + }); + ref.set(subscriber); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Subscriber<Object> subscriber = ref.get(); + subscriber.onNext(1); + subscriber.onComplete(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeMainBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + final TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + final AtomicInteger counter = new AtomicInteger(); + subscriber.onSubscribe(new Subscription() { + + @Override + public void cancel() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public void request(long n) { + } + }); + ref.set(subscriber); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Subscriber<Object> subscriber = ref.get(); + subscriber.onNext(1); + subscriber.onError(ex); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void boundarySupplierDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Flowable<Object>>>() { + @Override + public Flowable<Flowable<Object>> apply(Flowable<Object> f) + throws Exception { + return f.window(Functions.justCallable(Flowable.never())).takeLast(1); + } + }); + } + + @Test + public void selectorUpstreamDisposedWhenOutputsDisposed() { + PublishProcessor<Integer> source = PublishProcessor.create(); + PublishProcessor<Integer> boundary = PublishProcessor.create(); + + TestSubscriber<Integer> ts = source.window(Functions.justCallable(boundary)) + .take(1) + .flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply( + Flowable<Integer> w) throws Exception { + return w.take(1); + } + }) + .test(); + + source.onNext(1); + + assertFalse("source not disposed", source.hasSubscribers()); + assertFalse("boundary not disposed", boundary.hasSubscribers()); + + ts.assertResult(1); + } + + @Test + public void supplierMainAndBoundaryBothError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = Flowable.error(new TestException("main")) + .window(Functions.justCallable(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + })) + .test(); + + ts + .assertValueCount(1) + .assertError(TestException.class) + .assertErrorMessage("main") + .assertNotComplete(); + + ref.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void supplierMainCompleteBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(Functions.justCallable(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onComplete(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onError(ex); + } + }; + + TestHelper.race(r1, r2); + + ts + .assertValueCount(1) + .assertTerminated(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void supplierMainNextBoundaryNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(Functions.justCallable(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onNext(1); + } + }; + + TestHelper.race(r1, r2); + + ts + .assertValueCount(2) + .assertNotComplete() + .assertNoErrors(); + } + } + + @Test + public void supplierTakeOneAnotherBoundary() { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(Functions.justCallable(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + ref.set(subscriber); + } + })) + .test(); + + ts.assertValueCount(1) + .assertNotTerminated() + .cancel(); + + ref.get().onNext(1); + + ts.assertValueCount(1) + .assertNotTerminated(); + } + + @Test + public void supplierDisposeMainBoundaryCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + final TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(Functions.justCallable(new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + final AtomicInteger counter = new AtomicInteger(); + subscriber.onSubscribe(new Subscription() { + + @Override + public void cancel() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public void request(long n) { + } + }); + ref.set(subscriber); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Subscriber<Object> subscriber = ref.get(); + subscriber.onNext(1); + subscriber.onComplete(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void supplierDisposeMainBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<? super Object>> refMain = new AtomicReference<Subscriber<? super Object>>(); + final AtomicReference<Subscriber<? super Object>> ref = new AtomicReference<Subscriber<? super Object>>(); + + final TestSubscriber<Flowable<Object>> ts = new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + refMain.set(subscriber); + } + } + .window(new Callable<Flowable<Object>>() { + int count; + @Override + public Flowable<Object> call() throws Exception { + if (++count > 1) { + return Flowable.never(); + } + return (new Flowable<Object>() { + @Override + protected void subscribeActual(Subscriber<? super Object> subscriber) { + final AtomicInteger counter = new AtomicInteger(); + subscriber.onSubscribe(new Subscription() { + + @Override + public void cancel() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public void request(long n) { + } + }); + ref.set(subscriber); + } + }); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Subscriber<Object> subscriber = ref.get(); + subscriber.onNext(1); + subscriber.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java index 32f6443233..af412d9a02 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -204,13 +203,14 @@ public void testBackpressureOuter() { final List<Integer> list = new ArrayList<Integer>(); - final Subscriber<Integer> o = TestHelper.mockSubscriber(); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); source.subscribe(new DefaultSubscriber<Flowable<Integer>>() { @Override public void onStart() { request(1); } + @Override public void onNext(Flowable<Integer> t) { t.subscribe(new DefaultSubscriber<Integer>() { @@ -218,30 +218,34 @@ public void onNext(Flowable<Integer> t) { public void onNext(Integer t) { list.add(t); } + @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } + @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); } + @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } + @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); assertEquals(Arrays.asList(1, 2, 3), list); - verify(o, never()).onError(any(Throwable.class)); - verify(o, times(1)).onComplete(); // 1 inner + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); // 1 inner } public static Flowable<Integer> hotStream() { @@ -343,22 +347,22 @@ public void dispose() { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Flowable<Object>>>() { @Override - public Flowable<Flowable<Object>> apply(Flowable<Object> o) throws Exception { - return o.window(1); + public Flowable<Flowable<Object>> apply(Flowable<Object> f) throws Exception { + return f.window(1); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Flowable<Object>>>() { @Override - public Flowable<Flowable<Object>> apply(Flowable<Object> o) throws Exception { - return o.window(2, 1); + public Flowable<Flowable<Object>> apply(Flowable<Object> f) throws Exception { + return f.window(2, 1); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Flowable<Object>>>() { @Override - public Flowable<Flowable<Object>> apply(Flowable<Object> o) throws Exception { - return o.window(1, 2); + public Flowable<Flowable<Object>> apply(Flowable<Object> f) throws Exception { + return f.window(1, 2); } }); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java index 5177276bed..1d27381129 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithStartEndFlowableTest.java @@ -17,15 +17,17 @@ import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.*; @@ -48,24 +50,24 @@ public void testFlowableBasedOpenerAndCloser() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 500); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 60); + push(subscriber, "three", 110); + push(subscriber, "four", 160); + push(subscriber, "five", 210); + complete(subscriber, 500); } }); Flowable<Object> openings = Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, new Object(), 50); - push(observer, new Object(), 200); - complete(observer, 250); + public void subscribe(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, new Object(), 50); + push(subscriber, new Object(), 200); + complete(subscriber, 250); } }); @@ -74,10 +76,10 @@ public void subscribe(Subscriber<? super Object> observer) { public Flowable<Object> apply(Object opening) { return Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, new Object(), 100); - complete(observer, 101); + public void subscribe(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, new Object(), 100); + complete(subscriber, 101); } }); } @@ -99,14 +101,14 @@ public void testFlowableBasedCloser() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 250); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 60); + push(subscriber, "three", 110); + push(subscriber, "four", 160); + push(subscriber, "five", 210); + complete(subscriber, 250); } }); @@ -116,16 +118,16 @@ public void subscribe(Subscriber<? super String> observer) { public Flowable<Object> call() { return Flowable.unsafeCreate(new Publisher<Object>() { @Override - public void subscribe(Subscriber<? super Object> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(Subscriber<? super Object> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); int c = calls++; if (c == 0) { - push(observer, new Object(), 100); + push(subscriber, new Object(), 100); } else if (c == 1) { - push(observer, new Object(), 100); + push(subscriber, new Object(), 100); } else { - complete(observer, 101); + complete(subscriber, 101); } } }); @@ -150,20 +152,20 @@ private List<String> list(String... args) { return list; } - private <T> void push(final Subscriber<T> observer, final T value, int delay) { + private <T> void push(final Subscriber<T> subscriber, final T value, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } - private void complete(final Subscriber<?> observer, int delay) { + private void complete(final Subscriber<?> subscriber, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } @@ -253,8 +255,8 @@ public Flowable<Integer> apply(Integer t) { ts.dispose(); - // FIXME subject has subscribers because of the open window - assertTrue(open.hasSubscribers()); + // Disposing the outer sequence stops the opening of new windows + assertFalse(open.hasSubscribers()); // FIXME subject has subscribers because of the open window assertTrue(close.hasSubscribers()); } @@ -268,7 +270,7 @@ public void dispose() { public void reentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -286,11 +288,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -299,8 +301,8 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { public void badSourceCallable() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override - public Object apply(Flowable<Object> o) throws Exception { - return o.window(Flowable.just(1), Functions.justFunction(Flowable.never())); + public Object apply(Flowable<Object> f) throws Exception { + return f.window(Flowable.just(1), Functions.justFunction(Flowable.never())); } }, false, 1, 1, (Object[])null); } @@ -311,7 +313,7 @@ public void boundarySelectorNormal() { PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); - TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { + TestSubscriber<Integer> ts = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; @@ -338,7 +340,7 @@ public Flowable<Integer> apply(Integer v) throws Exception { TestHelper.emit(source, 7, 8); - to.assertResult(1, 2, 3, 4, 5, 5, 6, 6, 7, 8); + ts.assertResult(1, 2, 3, 4, 5, 5, 6, 6, 7, 8); } @Test @@ -347,7 +349,7 @@ public void startError() { PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); - TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { + TestSubscriber<Integer> ts = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; @@ -358,7 +360,7 @@ public Flowable<Integer> apply(Integer v) throws Exception { start.onError(new TestException()); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); assertFalse("Source has observers!", source.hasSubscribers()); assertFalse("Start has observers!", start.hasSubscribers()); @@ -371,7 +373,7 @@ public void endError() { PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); - TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { + TestSubscriber<Integer> ts = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; @@ -383,7 +385,7 @@ public Flowable<Integer> apply(Integer v) throws Exception { start.onNext(1); end.onError(new TestException()); - to.assertFailure(TestException.class); + ts.assertFailure(TestException.class); assertFalse("Source has observers!", source.hasSubscribers()); assertFalse("Start has observers!", start.hasSubscribers()); @@ -398,4 +400,89 @@ public void mainError() { .test() .assertFailure(TestException.class); } + + @Test + public void windowCloseIngoresCancel() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor.createDefault(1) + .window(BehaviorProcessor.createDefault(1), new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer f) throws Exception { + return new Flowable<Integer>() { + @Override + protected void subscribeActual( + Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + s.onError(new TestException()); + } + }; + } + }) + .test() + .assertValueCount(1) + .assertNoErrors() + .assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static Flowable<Integer> flowableDisposed(final AtomicBoolean ref) { + return Flowable.just(1).concatWith(Flowable.<Integer>never()) + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + ref.set(true); + } + }); + } + + @Test + public void mainAndBoundaryDisposeOnNoWindows() { + AtomicBoolean mainDisposed = new AtomicBoolean(); + AtomicBoolean openDisposed = new AtomicBoolean(); + final AtomicBoolean closeDisposed = new AtomicBoolean(); + + flowableDisposed(mainDisposed) + .window(flowableDisposed(openDisposed), new Function<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Integer v) throws Exception { + return flowableDisposed(closeDisposed); + } + }) + .test() + .assertSubscribed() + .assertNoErrors() + .assertNotComplete() + .dispose(); + + assertTrue(mainDisposed.get()); + assertTrue(openDisposed.get()); + assertTrue(closeDisposed.get()); + } + + @Test + @SuppressWarnings("unchecked") + public void mainWindowMissingBackpressure() { + PublishProcessor<Integer> source = PublishProcessor.create(); + PublishProcessor<Integer> boundary = PublishProcessor.create(); + + TestSubscriber<Flowable<Integer>> ts = source.window(boundary, Functions.justFunction(Flowable.never())) + .test(0L) + ; + + ts.assertEmpty(); + + boundary.onNext(1); + + ts.assertFailure(MissingBackpressureException.class); + + assertFalse(source.hasSubscribers()); + assertFalse(boundary.hasSubscribers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java index 74bcd72152..56e1a736e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java @@ -16,7 +16,7 @@ import static org.junit.Assert.*; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; @@ -27,11 +27,11 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.*; import io.reactivex.subscribers.*; - public class FlowableWindowWithTimeTest { private TestScheduler scheduler; @@ -50,31 +50,33 @@ public void testTimedAndCount() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 10); - push(observer, "two", 90); - push(observer, "three", 110); - push(observer, "four", 190); - push(observer, "five", 210); - complete(observer, 250); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 10); + push(subscriber, "two", 90); + push(subscriber, "three", 110); + push(subscriber, "four", 190); + push(subscriber, "five", 210); + complete(subscriber, 250); } }); Flowable<Flowable<String>> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler, 2); windowed.subscribe(observeWindow(list, lists)); - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(95, TimeUnit.MILLISECONDS); assertEquals(1, lists.size()); assertEquals(lists.get(0), list("one", "two")); - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("three", "four")); + scheduler.advanceTimeTo(195, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertTrue(lists.get(1).isEmpty()); + assertEquals(lists.get(2), list("three", "four")); scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(2), list("five")); + assertEquals(5, lists.size()); + assertTrue(lists.get(3).isEmpty()); + assertEquals(lists.get(4), list("five")); } @Test @@ -84,14 +86,14 @@ public void testTimed() { Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - push(observer, "one", 98); - push(observer, "two", 99); - push(observer, "three", 99); // FIXME happens after the window is open - push(observer, "four", 101); - push(observer, "five", 102); - complete(observer, 150); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + push(subscriber, "one", 98); + push(subscriber, "two", 99); + push(subscriber, "three", 99); // FIXME happens after the window is open + push(subscriber, "four", 101); + push(subscriber, "five", 102); + complete(subscriber, 150); } }); @@ -115,20 +117,20 @@ private List<String> list(String... args) { return list; } - private <T> void push(final Subscriber<T> observer, final T value, int delay) { + private <T> void push(final Subscriber<T> subscriber, final T value, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onNext(value); + subscriber.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } - private void complete(final Subscriber<?> observer, int delay) { + private void complete(final Subscriber<?> subscriber, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { - observer.onComplete(); + subscriber.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } @@ -157,6 +159,7 @@ public void onNext(T args) { } }; } + @Test public void testExactWindowSize() { Flowable<Flowable<Integer>> source = Flowable.range(1, 10) @@ -221,7 +224,6 @@ public void accept(Integer pv) { Assert.assertTrue(ts.valueCount() != 0); } - @Test public void timespanTimeskipCustomSchedulerBufferSize() { Flowable.range(1, 10) @@ -366,47 +368,68 @@ public void timeskipOverlapping() { @Test public void exactOnError() { - TestScheduler scheduler = new TestScheduler(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler) - .flatMap(Functions.<Flowable<Integer>>identity()) - .test(); + TestSubscriber<Integer> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler) + .flatMap(Functions.<Flowable<Integer>>identity()) + .test(); - pp.onError(new TestException()); + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); - ts.assertFailure(TestException.class); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void overlappingOnError() { - TestScheduler scheduler = new TestScheduler(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) - .flatMap(Functions.<Flowable<Integer>>identity()) - .test(); + TestSubscriber<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) + .flatMap(Functions.<Flowable<Integer>>identity()) + .test(); - pp.onError(new TestException()); + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); - ts.assertFailure(TestException.class); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void skipOnError() { - TestScheduler scheduler = new TestScheduler(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler) - .flatMap(Functions.<Flowable<Integer>>identity()) - .test(); + TestSubscriber<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler) + .flatMap(Functions.<Flowable<Integer>>identity()) + .test(); - pp.onError(new TestException()); + pp.onError(new TestException()); - ts.assertFailure(TestException.class); + ts.assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -484,16 +507,23 @@ public void skipBackpressure2() { @Test public void overlapBackpressure2() { - TestScheduler scheduler = new TestScheduler(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> pp = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); - TestSubscriber<Flowable<Integer>> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) - .test(1L); + TestSubscriber<Flowable<Integer>> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) + .test(1L); - scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); - ts.assertError(MissingBackpressureException.class); + ts.assertError(MissingBackpressureException.class); + + TestHelper.assertError(errors, 0, MissingBackpressureException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -561,7 +591,7 @@ public void exactUnboundedReentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -579,11 +609,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -594,7 +624,7 @@ public void exactBoundedReentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -612,11 +642,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -627,7 +657,7 @@ public void exactBoundedReentrant2() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -645,11 +675,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -660,7 +690,7 @@ public void skipReentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -678,11 +708,11 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) - .subscribe(to); + .subscribe(ts); ps.onNext(1); - to + ts .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @@ -690,9 +720,9 @@ public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { @Test public void sizeTimeTimeout() { TestScheduler scheduler = new TestScheduler(); - PublishProcessor<Integer> ps = PublishProcessor.<Integer>create(); + PublishProcessor<Integer> pp = PublishProcessor.<Integer>create(); - TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 100) + TestSubscriber<Flowable<Integer>> ts = pp.window(5, TimeUnit.MILLISECONDS, scheduler, 100) .test() .assertValueCount(1); @@ -782,6 +812,7 @@ public void periodicWindowCompletionRestartTimerBoundedSomeData() { .assertNoErrors() .assertNotComplete(); } + @Test public void countRestartsOnTimeTick() { TestScheduler scheduler = new TestScheduler(); @@ -806,5 +837,325 @@ public void countRestartsOnTimeTick() { .assertNoErrors() .assertNotComplete(); } -} + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Flowable<Object>>>() { + @Override + public Publisher<Flowable<Object>> apply(Flowable<Object> f) + throws Exception { + return f.window(1, TimeUnit.SECONDS, 1).takeLast(0); + } + }); + } + + @SuppressWarnings("unchecked") + @Test + public void firstWindowMissingBackpressure() { + Flowable.never() + .window(1, TimeUnit.SECONDS, 1) + .test(0L) + .assertFailure(MissingBackpressureException.class); + } + + @Test + public void nextWindowMissingBackpressure() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Flowable<Integer>> ts = pp.window(1, TimeUnit.SECONDS, 1) + .test(1L); + + pp.onNext(1); + + ts.assertValueCount(1) + .assertError(MissingBackpressureException.class) + .assertNotComplete(); + } + + @Test + public void cancelUpfront() { + Flowable.never() + .window(1, TimeUnit.SECONDS, 1) + .test(0L, true) + .assertEmpty(); + } + + @Test + public void nextWindowMissingBackpressureDrainOnSize() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Flowable<Integer>> ts = pp.window(1, TimeUnit.MINUTES, 1) + .subscribeWith(new TestSubscriber<Flowable<Integer>>(2) { + int calls; + @Override + public void onNext(Flowable<Integer> t) { + super.onNext(t); + if (++calls == 2) { + pp.onNext(2); + } + } + }); + + pp.onNext(1); + + ts.assertValueCount(2) + .assertError(MissingBackpressureException.class) + .assertNotComplete(); + } + + @Test + public void nextWindowMissingBackpressureDrainOnTime() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestScheduler sch = new TestScheduler(); + + TestSubscriber<Flowable<Integer>> ts = pp.window(1, TimeUnit.MILLISECONDS, sch, 10) + .test(1); + + sch.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + ts.assertValueCount(1) + .assertError(MissingBackpressureException.class) + .assertNotComplete(); + } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Flowable<Integer>>() { + int count; + @Override + public void accept(Flowable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java index b4bb8afcde..c534ddd990 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -51,35 +50,35 @@ public void testSimple() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); - Subscriber<Integer> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); - result.subscribe(o); + result.subscribe(subscriber); source.onNext(1); - inOrder.verify(o, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onNext(anyInt()); other.onNext(1); - inOrder.verify(o, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onNext(anyInt()); source.onNext(2); - inOrder.verify(o).onNext((2 << 8) + 1); + inOrder.verify(subscriber).onNext((2 << 8) + 1); other.onNext(2); - inOrder.verify(o, never()).onNext(anyInt()); + inOrder.verify(subscriber, never()).onNext(anyInt()); other.onComplete(); - inOrder.verify(o, never()).onComplete(); + inOrder.verify(subscriber, never()).onComplete(); source.onNext(3); - inOrder.verify(o).onNext((3 << 8) + 2); + inOrder.verify(subscriber).onNext((3 << 8) + 2); source.onComplete(); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -134,7 +133,6 @@ public void testEmptyOther() { assertFalse(other.hasSubscribers()); } - @Test public void testUnsubscription() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -189,6 +187,7 @@ public void testSourceThrows() { assertFalse(source.hasSubscribers()); assertFalse(other.hasSubscribers()); } + @Test public void testOtherThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); @@ -261,7 +260,7 @@ public void testNoDownstreamUnsubscribe() { @Test public void testBackpressure() { - Flowable<Integer> source = Flowable.range(1, 10); + PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> other = PublishProcessor.create(); Flowable<Integer> result = source.withLatestFrom(other, COMBINER); @@ -274,17 +273,24 @@ public void testBackpressure() { ts.request(1); + source.onNext(1); + assertTrue("Other has no observers!", other.hasSubscribers()); ts.assertNoValues(); other.onNext(1); - ts.request(1); + source.onNext(2); ts.assertValue((2 << 8) + 1); ts.request(5); + source.onNext(3); + source.onNext(4); + source.onNext(5); + source.onNext(6); + source.onNext(7); ts.assertValues( (2 << 8) + 1, (3 << 8) + 1, (4 << 8) + 1, (5 << 8) + 1, (6 << 8) + 1, (7 << 8) + 1 @@ -306,36 +312,36 @@ public String apply(Object[] args) { @Test public void manySources() { - PublishProcessor<String> ps1 = PublishProcessor.create(); - PublishProcessor<String> ps2 = PublishProcessor.create(); - PublishProcessor<String> ps3 = PublishProcessor.create(); + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); + PublishProcessor<String> pp3 = PublishProcessor.create(); PublishProcessor<String> main = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(); - main.withLatestFrom(new Flowable[] { ps1, ps2, ps3 }, toArray) + main.withLatestFrom(new Flowable[] { pp1, pp2, pp3 }, toArray) .subscribe(ts); main.onNext("1"); ts.assertNoValues(); - ps1.onNext("a"); + pp1.onNext("a"); ts.assertNoValues(); - ps2.onNext("A"); + pp2.onNext("A"); ts.assertNoValues(); - ps3.onNext("="); + pp3.onNext("="); ts.assertNoValues(); main.onNext("2"); ts.assertValues("[2, a, A, =]"); - ps2.onNext("B"); + pp2.onNext("B"); ts.assertValues("[2, a, A, =]"); - ps3.onComplete(); + pp3.onComplete(); ts.assertValues("[2, a, A, =]"); - ps1.onNext("b"); + pp1.onNext("b"); main.onNext("3"); @@ -346,43 +352,43 @@ public void manySources() { ts.assertNoErrors(); ts.assertComplete(); - assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); - assertFalse("ps3 has subscribers?", ps3.hasSubscribers()); + assertFalse("ps1 has subscribers?", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?", pp2.hasSubscribers()); + assertFalse("ps3 has subscribers?", pp3.hasSubscribers()); } @Test public void manySourcesIterable() { - PublishProcessor<String> ps1 = PublishProcessor.create(); - PublishProcessor<String> ps2 = PublishProcessor.create(); - PublishProcessor<String> ps3 = PublishProcessor.create(); + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); + PublishProcessor<String> pp3 = PublishProcessor.create(); PublishProcessor<String> main = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(); - main.withLatestFrom(Arrays.<Flowable<?>>asList(ps1, ps2, ps3), toArray) + main.withLatestFrom(Arrays.<Flowable<?>>asList(pp1, pp2, pp3), toArray) .subscribe(ts); main.onNext("1"); ts.assertNoValues(); - ps1.onNext("a"); + pp1.onNext("a"); ts.assertNoValues(); - ps2.onNext("A"); + pp2.onNext("A"); ts.assertNoValues(); - ps3.onNext("="); + pp3.onNext("="); ts.assertNoValues(); main.onNext("2"); ts.assertValues("[2, a, A, =]"); - ps2.onNext("B"); + pp2.onNext("B"); ts.assertValues("[2, a, A, =]"); - ps3.onComplete(); + pp3.onComplete(); ts.assertValues("[2, a, A, =]"); - ps1.onNext("b"); + pp1.onNext("b"); main.onNext("3"); @@ -393,9 +399,9 @@ public void manySourcesIterable() { ts.assertNoErrors(); ts.assertComplete(); - assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); - assertFalse("ps3 has subscribers?", ps3.hasSubscribers()); + assertFalse("ps1 has subscribers?", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?", pp2.hasSubscribers()); + assertFalse("ps3 has subscribers?", pp3.hasSubscribers()); } @Test @@ -432,12 +438,12 @@ public void manySourcesIterableSweep() { @Test public void backpressureNoSignal() { - PublishProcessor<String> ps1 = PublishProcessor.create(); - PublishProcessor<String> ps2 = PublishProcessor.create(); + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(0); - Flowable.range(1, 10).withLatestFrom(new Flowable<?>[] { ps1, ps2 }, toArray) + Flowable.range(1, 10).withLatestFrom(new Flowable<?>[] { pp1, pp2 }, toArray) .subscribe(ts); ts.assertNoValues(); @@ -448,24 +454,24 @@ public void backpressureNoSignal() { ts.assertNoErrors(); ts.assertComplete(); - assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); + assertFalse("ps1 has subscribers?", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?", pp2.hasSubscribers()); } @Test public void backpressureWithSignal() { - PublishProcessor<String> ps1 = PublishProcessor.create(); - PublishProcessor<String> ps2 = PublishProcessor.create(); + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); TestSubscriber<String> ts = new TestSubscriber<String>(0); - Flowable.range(1, 3).withLatestFrom(new Flowable<?>[] { ps1, ps2 }, toArray) + Flowable.range(1, 3).withLatestFrom(new Flowable<?>[] { pp1, pp2 }, toArray) .subscribe(ts); ts.assertNoValues(); - ps1.onNext("1"); - ps2.onNext("1"); + pp1.onNext("1"); + pp2.onNext("1"); ts.request(1); @@ -481,8 +487,8 @@ public void backpressureWithSignal() { ts.assertNoErrors(); ts.assertComplete(); - assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); - assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); + assertFalse("ps1 has subscribers?", pp1.hasSubscribers()); + assertFalse("ps2 has subscribers?", pp2.hasSubscribers()); } @Test @@ -634,12 +640,12 @@ public void manyErrors() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onError(new TestException("First")); - observer.onNext(1); - observer.onError(new TestException("Second")); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onError(new TestException("First")); + subscriber.onNext(1); + subscriber.onError(new TestException("Second")); + subscriber.onComplete(); } }.withLatestFrom(Flowable.just(2), Flowable.just(3), new Function3<Integer, Integer, Integer, Object>() { @Override @@ -717,4 +723,142 @@ public void zeroOtherCombinerReturnsNull() { .test() .assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value"); } + + @Test + public void singleRequestNotForgottenWhenNoData() { + PublishProcessor<Integer> source = PublishProcessor.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + Flowable<Integer> result = source.withLatestFrom(other, COMBINER); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); + + result.subscribe(ts); + + ts.request(1); + + source.onNext(1); + + ts.assertNoValues(); + + other.onNext(1); + + ts.assertNoValues(); + + source.onNext(2); + + ts.assertValue((2 << 8) + 1); + } + + @Test + public void coldSourceConsumedWithoutOther() { + Flowable.range(1, 10).withLatestFrom(Flowable.never(), + new BiFunction<Integer, Object, Object>() { + @Override + public Object apply(Integer a, Object b) throws Exception { + return a; + } + }) + .test(1) + .assertResult(); + } + + @Test + public void coldSourceConsumedWithoutManyOthers() { + Flowable.range(1, 10).withLatestFrom(Flowable.never(), Flowable.never(), Flowable.never(), + new Function4<Integer, Object, Object, Object, Object>() { + @Override + public Object apply(Integer a, Object b, Object c, Object d) throws Exception { + return a; + } + }) + .test(1) + .assertResult(); + } + + @Test + public void otherOnSubscribeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp0 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp3 = PublishProcessor.create(); + + final Flowable<Object> source = pp0.withLatestFrom(pp1, pp2, pp3, new Function4<Object, Integer, Integer, Integer, Object>() { + @Override + public Object apply(Object a, Integer b, Integer c, Integer d) + throws Exception { + return a; + } + }); + + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.subscribe(ts); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertEmpty(); + + assertFalse(pp0.hasSubscribers()); + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + assertFalse(pp3.hasSubscribers()); + } + } + + @Test + public void otherCompleteRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp0 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp3 = PublishProcessor.create(); + + final Flowable<Object> source = pp0.withLatestFrom(pp1, pp2, pp3, new Function4<Object, Integer, Integer, Integer, Object>() { + @Override + public Object apply(Object a, Integer b, Integer c, Integer d) + throws Exception { + return a; + } + }); + + final TestSubscriber<Object> ts = new TestSubscriber<Object>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.subscribe(ts); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + pp1.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(); + + assertFalse(pp0.hasSubscribers()); + assertFalse(pp1.hasSubscribers()); + assertFalse(pp2.hasSubscribers()); + assertFalse(pp3.hasSubscribers()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipCompletionTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipCompletionTest.java index 546fdfe62b..6c46093017 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipCompletionTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipCompletionTest.java @@ -35,7 +35,7 @@ public class FlowableZipCompletionTest { PublishProcessor<String> s2; Flowable<String> zipped; - Subscriber<String> observer; + Subscriber<String> subscriber; InOrder inOrder; @Before @@ -51,10 +51,10 @@ public String apply(String t1, String t2) { s2 = PublishProcessor.create(); zipped = Flowable.zip(s1, s2, concat2Strings); - observer = TestHelper.mockSubscriber(); - inOrder = inOrder(observer); + subscriber = TestHelper.mockSubscriber(); + inOrder = inOrder(subscriber); - zipped.subscribe(observer); + zipped.subscribe(subscriber); } @Test @@ -63,10 +63,10 @@ public void testFirstCompletesThenSecondInfinite() { s1.onNext("b"); s1.onComplete(); s2.onNext("1"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s2.onNext("2"); - inOrder.verify(observer, times(1)).onNext("b-2"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -75,11 +75,11 @@ public void testSecondInfiniteThenFirstCompletes() { s2.onNext("1"); s2.onNext("2"); s1.onNext("a"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s1.onNext("b"); - inOrder.verify(observer, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onNext("b-2"); s1.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -89,10 +89,10 @@ public void testSecondCompletesThenFirstInfinite() { s2.onNext("2"); s2.onComplete(); s1.onNext("a"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s1.onNext("b"); - inOrder.verify(observer, times(1)).onNext("b-2"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -101,11 +101,11 @@ public void testFirstInfiniteThenSecondCompletes() { s1.onNext("a"); s1.onNext("b"); s2.onNext("1"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s2.onNext("2"); - inOrder.verify(observer, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onNext("b-2"); s2.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java index c273d8be14..2ca5481fc3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -38,7 +37,7 @@ public class FlowableZipIterableTest { PublishProcessor<String> s2; Flowable<String> zipped; - Subscriber<String> observer; + Subscriber<String> subscriber; InOrder inOrder; @Before @@ -54,10 +53,10 @@ public String apply(String t1, String t2) { s2 = PublishProcessor.create(); zipped = Flowable.zip(s1, s2, concat2Strings); - observer = TestHelper.mockSubscriber(); - inOrder = inOrder(observer); + subscriber = TestHelper.mockSubscriber(); + inOrder = inOrder(subscriber); - zipped.subscribe(observer); + zipped.subscribe(subscriber); } BiFunction<Object, Object, String> zipr2 = new BiFunction<Object, Object, String>() { @@ -81,24 +80,24 @@ public String apply(Object t1, Object t2, Object t3) { public void testZipIterableSameSize() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList("1", "2", "3"); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onNext("three-"); r1.onComplete(); - io.verify(o).onNext("one-1"); - io.verify(o).onNext("two-2"); - io.verify(o).onNext("three-3"); - io.verify(o).onComplete(); + io.verify(subscriber).onNext("one-1"); + io.verify(subscriber).onNext("two-2"); + io.verify(subscriber).onNext("three-3"); + io.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -106,19 +105,19 @@ public void testZipIterableSameSize() { public void testZipIterableEmptyFirstSize() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList("1", "2", "3"); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onComplete(); - io.verify(o).onComplete(); + io.verify(subscriber).onComplete(); - verify(o, never()).onNext(any(String.class)); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -126,44 +125,44 @@ public void testZipIterableEmptyFirstSize() { public void testZipIterableEmptySecond() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList(); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onNext("three-"); r1.onComplete(); - io.verify(o).onComplete(); + io.verify(subscriber).onComplete(); - verify(o, never()).onNext(any(String.class)); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test public void testZipIterableFirstShorter() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList("1", "2", "3"); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onComplete(); - io.verify(o).onNext("one-1"); - io.verify(o).onNext("two-2"); - io.verify(o).onComplete(); + io.verify(subscriber).onNext("one-1"); + io.verify(subscriber).onNext("two-2"); + io.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -171,23 +170,23 @@ public void testZipIterableFirstShorter() { public void testZipIterableSecondShorter() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList("1", "2"); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onNext("three-"); r1.onComplete(); - io.verify(o).onNext("one-1"); - io.verify(o).onNext("two-2"); - io.verify(o).onComplete(); + io.verify(subscriber).onNext("one-1"); + io.verify(subscriber).onNext("two-2"); + io.verify(subscriber).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } @@ -195,22 +194,22 @@ public void testZipIterableSecondShorter() { public void testZipIterableFirstThrows() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = Arrays.asList("1", "2", "3"); - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onError(new TestException()); - io.verify(o).onNext("one-1"); - io.verify(o).onNext("two-2"); - io.verify(o).onError(any(TestException.class)); + io.verify(subscriber).onNext("one-1"); + io.verify(subscriber).onNext("two-2"); + io.verify(subscriber).onError(any(TestException.class)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onComplete(); } @@ -218,8 +217,8 @@ public void testZipIterableFirstThrows() { public void testZipIterableIteratorThrows() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = new Iterable<String>() { @Override @@ -228,16 +227,16 @@ public Iterator<String> iterator() { } }; - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onNext("two-"); r1.onError(new TestException()); - io.verify(o).onError(any(TestException.class)); + io.verify(subscriber).onError(any(TestException.class)); - verify(o, never()).onComplete(); - verify(o, never()).onNext(any(String.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any(String.class)); } @@ -245,8 +244,8 @@ public Iterator<String> iterator() { public void testZipIterableHasNextThrows() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = new Iterable<String>() { @@ -279,15 +278,15 @@ public void remove() { }; - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onNext("one-"); r1.onError(new TestException()); - io.verify(o).onNext("one-1"); - io.verify(o).onError(any(TestException.class)); + io.verify(subscriber).onNext("one-1"); + io.verify(subscriber).onError(any(TestException.class)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onComplete(); } @@ -295,8 +294,8 @@ public void remove() { public void testZipIterableNextThrows() { PublishProcessor<String> r1 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> o = TestHelper.mockSubscriber(); - InOrder io = inOrder(o); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + InOrder io = inOrder(subscriber); Iterable<String> r2 = new Iterable<String>() { @@ -323,14 +322,14 @@ public void remove() { }; - r1.zipWith(r2, zipr2).subscribe(o); + r1.zipWith(r2, zipr2).subscribe(subscriber); r1.onError(new TestException()); - io.verify(o).onError(any(TestException.class)); + io.verify(subscriber).onError(any(TestException.class)); - verify(o, never()).onNext(any(String.class)); - verify(o, never()).onComplete(); + verify(subscriber, never()).onNext(any(String.class)); + verify(subscriber, never()).onComplete(); } @@ -353,12 +352,12 @@ public String apply(Integer t1) { @Test public void testTake2() { - Flowable<Integer> o = Flowable.just(1, 2, 3, 4, 5); + Flowable<Integer> f = Flowable.just(1, 2, 3, 4, 5); Iterable<String> it = Arrays.asList("a", "b", "c", "d", "e"); SquareStr squareStr = new SquareStr(); - o.map(squareStr).zipWith(it, concat2Strings).take(2).subscribe(printer); + f.map(squareStr).zipWith(it, concat2Strings).take(2).subscribe(printer); assertEquals(2, squareStr.counter.get()); } @@ -377,8 +376,8 @@ public Object apply(Integer a, Integer b) throws Exception { public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Integer>, Flowable<Object>>() { @Override - public Flowable<Object> apply(Flowable<Integer> o) throws Exception { - return o.zipWith(Arrays.asList(1), new BiFunction<Integer, Integer, Object>() { + public Flowable<Object> apply(Flowable<Integer> f) throws Exception { + return f.zipWith(Arrays.asList(1), new BiFunction<Integer, Integer, Object>() { @Override public Object apply(Integer a, Integer b) throws Exception { return a + b; @@ -406,13 +405,13 @@ public void badSource() { try { new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onComplete(); - observer.onNext(2); - observer.onError(new TestException()); - observer.onComplete(); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); } } .zipWith(Arrays.asList(1), new BiFunction<Integer, Integer, Object>() { diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java index d7304d26ff..ee691d93c2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.*; @@ -43,7 +42,7 @@ public class FlowableZipTest { PublishProcessor<String> s2; Flowable<String> zipped; - Subscriber<String> observer; + Subscriber<String> subscriber; InOrder inOrder; @Before @@ -59,10 +58,10 @@ public String apply(String t1, String t2) { s2 = PublishProcessor.create(); zipped = Flowable.zip(s1, s2, concat2Strings); - observer = TestHelper.mockSubscriber(); - inOrder = inOrder(observer); + subscriber = TestHelper.mockSubscriber(); + inOrder = inOrder(subscriber); - zipped.subscribe(observer); + zipped.subscribe(subscriber); } @SuppressWarnings("unchecked") @@ -72,16 +71,16 @@ public void testCollectionSizeDifferentThanFunction() { //Function3<String, Integer, int[], String> /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); @SuppressWarnings("rawtypes") Collection ws = java.util.Collections.singleton(Flowable.just("one", "two")); Flowable<String> w = Flowable.zip(ws, zipr); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, never()).onNext(any(String.class)); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, never()).onNext(any(String.class)); } @Test @@ -99,18 +98,18 @@ public void testStartpingDifferentLengthFlowableSequences1() { /* simulate sending data */ // once for w1 - w1.observer.onNext("1a"); - w1.observer.onComplete(); + w1.subscriber.onNext("1a"); + w1.subscriber.onComplete(); // twice for w2 - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w2.observer.onComplete(); + w2.subscriber.onNext("2a"); + w2.subscriber.onNext("2b"); + w2.subscriber.onComplete(); // 4 times for w3 - w3.observer.onNext("3a"); - w3.observer.onNext("3b"); - w3.observer.onNext("3c"); - w3.observer.onNext("3d"); - w3.observer.onComplete(); + w3.subscriber.onNext("3a"); + w3.subscriber.onNext("3b"); + w3.subscriber.onNext("3c"); + w3.subscriber.onNext("3d"); + w3.subscriber.onComplete(); /* we should have been called 1 time on the Subscriber */ InOrder io = inOrder(w); @@ -132,18 +131,18 @@ public void testStartpingDifferentLengthFlowableSequences2() { /* simulate sending data */ // 4 times for w1 - w1.observer.onNext("1a"); - w1.observer.onNext("1b"); - w1.observer.onNext("1c"); - w1.observer.onNext("1d"); - w1.observer.onComplete(); + w1.subscriber.onNext("1a"); + w1.subscriber.onNext("1b"); + w1.subscriber.onNext("1c"); + w1.subscriber.onNext("1d"); + w1.subscriber.onComplete(); // twice for w2 - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w2.observer.onComplete(); + w2.subscriber.onNext("2a"); + w2.subscriber.onNext("2b"); + w2.subscriber.onComplete(); // 1 times for w3 - w3.observer.onNext("3a"); - w3.observer.onComplete(); + w3.subscriber.onNext("3a"); + w3.subscriber.onComplete(); /* we should have been called 1 time on the Subscriber */ InOrder io = inOrder(w); @@ -178,32 +177,32 @@ public void testAggregatorSimple() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("hello"); r2.onNext("world"); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - inOrder.verify(observer, times(1)).onNext("helloworld"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("helloworld"); r1.onNext("hello "); r2.onNext("again"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - inOrder.verify(observer, times(1)).onNext("hello again"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("hello again"); r1.onComplete(); r2.onComplete(); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyString()); + verify(subscriber, times(1)).onComplete(); } @Test @@ -213,26 +212,26 @@ public void testAggregatorDifferentSizedResultsWithOnComplete() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("hello"); r2.onNext("world"); r2.onComplete(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, times(1)).onNext("helloworld"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("helloworld"); + inOrder.verify(subscriber, times(1)).onComplete(); r1.onNext("hi"); r1.onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, never()).onNext(anyString()); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyString()); } @Test @@ -240,27 +239,27 @@ public void testAggregateMultipleTypes() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<Integer> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("hello"); r2.onNext(1); r2.onComplete(); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, times(1)).onNext("hello1"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onNext("hello1"); + inOrder.verify(subscriber, times(1)).onComplete(); r1.onNext("hi"); r1.onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, never()).onNext(anyString()); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyString()); } @Test @@ -269,18 +268,18 @@ public void testAggregate3Types() { PublishProcessor<Integer> r2 = PublishProcessor.create(); PublishProcessor<List<Integer>> r3 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, r3, zipr3).subscribe(observer); + Flowable.zip(r1, r2, r3, zipr3).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("hello"); r2.onNext(2); r3.onNext(Arrays.asList(5, 6, 7)); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onNext("hello2[5, 6, 7]"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onNext("hello2[5, 6, 7]"); } @Test @@ -288,9 +287,9 @@ public void testAggregatorsWithDifferentSizesAndTiming() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("one"); @@ -298,24 +297,24 @@ public void testAggregatorsWithDifferentSizesAndTiming() { r1.onNext("three"); r2.onNext("A"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onNext("oneA"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onNext("oneA"); r1.onNext("four"); r1.onComplete(); r2.onNext("B"); - verify(observer, times(1)).onNext("twoB"); + verify(subscriber, times(1)).onNext("twoB"); r2.onNext("C"); - verify(observer, times(1)).onNext("threeC"); + verify(subscriber, times(1)).onNext("threeC"); r2.onNext("D"); - verify(observer, times(1)).onNext("fourD"); + verify(subscriber, times(1)).onNext("fourD"); r2.onNext("E"); - verify(observer, never()).onNext("E"); + verify(subscriber, never()).onNext("E"); r2.onComplete(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @@ -323,26 +322,26 @@ public void testAggregatorError() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("hello"); r2.onNext("world"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onNext("helloworld"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onNext("helloworld"); r1.onError(new RuntimeException("")); r1.onNext("hello"); r2.onNext("again"); - verify(observer, times(1)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, times(1)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); // we don't want to be called again after an error - verify(observer, times(0)).onNext("helloagain"); + verify(subscriber, times(0)).onNext("helloagain"); } @Test @@ -350,8 +349,8 @@ public void testAggregatorUnsubscribe() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); Flowable.zip(r1, r2, zipr2).subscribe(ts); @@ -359,18 +358,18 @@ public void testAggregatorUnsubscribe() { r1.onNext("hello"); r2.onNext("world"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); - verify(observer, times(1)).onNext("helloworld"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); + verify(subscriber, times(1)).onNext("helloworld"); ts.dispose(); r1.onNext("hello"); r2.onNext("again"); - verify(observer, times(0)).onError(any(Throwable.class)); - verify(observer, never()).onComplete(); + verify(subscriber, times(0)).onError(any(Throwable.class)); + verify(subscriber, never()).onComplete(); // we don't want to be called again after an error - verify(observer, times(0)).onNext("helloagain"); + verify(subscriber, times(0)).onNext("helloagain"); } @Test @@ -378,9 +377,9 @@ public void testAggregatorEarlyCompletion() { PublishProcessor<String> r1 = PublishProcessor.create(); PublishProcessor<String> r2 = PublishProcessor.create(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - Flowable.zip(r1, r2, zipr2).subscribe(observer); + Flowable.zip(r1, r2, zipr2).subscribe(subscriber); /* simulate the Flowables pushing data into the aggregator */ r1.onNext("one"); @@ -388,17 +387,17 @@ public void testAggregatorEarlyCompletion() { r1.onComplete(); r2.onNext("A"); - InOrder inOrder = inOrder(observer); + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, times(1)).onNext("oneA"); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("oneA"); r2.onComplete(); - inOrder.verify(observer, never()).onError(any(Throwable.class)); - inOrder.verify(observer, times(1)).onComplete(); - inOrder.verify(observer, never()).onNext(anyString()); + inOrder.verify(subscriber, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onComplete(); + inOrder.verify(subscriber, never()).onNext(anyString()); } @Test @@ -406,16 +405,16 @@ public void testStart2Types() { BiFunction<String, Integer, String> zipr = getConcatStringIntegerZipr(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> w = Flowable.zip(Flowable.just("one", "two"), Flowable.just(2, 3, 4), zipr); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one2"); - verify(observer, times(1)).onNext("two3"); - verify(observer, never()).onNext("4"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one2"); + verify(subscriber, times(1)).onNext("two3"); + verify(subscriber, never()).onNext("4"); } @Test @@ -423,27 +422,27 @@ public void testStart3Types() { Function3<String, Integer, int[], String> zipr = getConcatStringIntegerIntArrayZipr(); /* define a Subscriber to receive aggregated events */ - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); Flowable<String> w = Flowable.zip(Flowable.just("one", "two"), Flowable.just(2), Flowable.just(new int[] { 4, 5, 6 }), zipr); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); - verify(observer, times(1)).onNext("one2[4, 5, 6]"); - verify(observer, never()).onNext("two"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one2[4, 5, 6]"); + verify(subscriber, never()).onNext("two"); } @Test public void testOnNextExceptionInvokesOnError() { BiFunction<Integer, Integer, Integer> zipr = getDivideZipr(); - Subscriber<Integer> observer = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); Flowable<Integer> w = Flowable.zip(Flowable.just(10, 20, 30), Flowable.just(0, 1, 2), zipr); - w.subscribe(observer); + w.subscribe(subscriber); - verify(observer, times(1)).onError(any(Throwable.class)); + verify(subscriber, times(1)).onError(any(Throwable.class)); } @Test @@ -453,8 +452,8 @@ public void testOnFirstCompletion() { Subscriber<String> obs = TestHelper.mockSubscriber(); - Flowable<String> o = Flowable.zip(oA, oB, getConcat2Strings()); - o.subscribe(obs); + Flowable<String> f = Flowable.zip(oA, oB, getConcat2Strings()); + f.subscribe(obs); InOrder io = inOrder(obs); @@ -503,8 +502,8 @@ public void testOnErrorTermination() { Subscriber<String> obs = TestHelper.mockSubscriber(); - Flowable<String> o = Flowable.zip(oA, oB, getConcat2Strings()); - o.subscribe(obs); + Flowable<String> f = Flowable.zip(oA, oB, getConcat2Strings()); + f.subscribe(obs); InOrder io = inOrder(obs); @@ -619,13 +618,13 @@ private static String getStringValue(Object o) { private static class TestFlowable implements Publisher<String> { - Subscriber<? super String> observer; + Subscriber<? super String> subscriber; @Override - public void subscribe(Subscriber<? super String> observer) { + public void subscribe(Subscriber<? super String> subscriber) { // just store the variable where it can be accessed so we can manually trigger it - this.observer = observer; - observer.onSubscribe(new BooleanSubscription()); + this.subscriber = subscriber; + subscriber.onSubscribe(new BooleanSubscription()); } } @@ -636,10 +635,10 @@ public void testFirstCompletesThenSecondInfinite() { s1.onNext("b"); s1.onComplete(); s2.onNext("1"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s2.onNext("2"); - inOrder.verify(observer, times(1)).onNext("b-2"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -648,11 +647,11 @@ public void testSecondInfiniteThenFirstCompletes() { s2.onNext("1"); s2.onNext("2"); s1.onNext("a"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s1.onNext("b"); - inOrder.verify(observer, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onNext("b-2"); s1.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -662,10 +661,10 @@ public void testSecondCompletesThenFirstInfinite() { s2.onNext("2"); s2.onComplete(); s1.onNext("a"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s1.onNext("b"); - inOrder.verify(observer, times(1)).onNext("b-2"); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -674,11 +673,11 @@ public void testFirstInfiniteThenSecondCompletes() { s1.onNext("a"); s1.onNext("b"); s2.onNext("1"); - inOrder.verify(observer, times(1)).onNext("a-1"); + inOrder.verify(subscriber, times(1)).onNext("a-1"); s2.onNext("2"); - inOrder.verify(observer, times(1)).onNext("b-2"); + inOrder.verify(subscriber, times(1)).onNext("b-2"); s2.onComplete(); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -687,14 +686,14 @@ public void testFirstFails() { s2.onNext("a"); s1.onError(new RuntimeException("Forced failure")); - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber, times(1)).onError(any(RuntimeException.class)); s2.onNext("b"); s1.onNext("1"); s1.onNext("2"); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, never()).onNext(any(String.class)); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(any(String.class)); inOrder.verifyNoMoreInteractions(); } @@ -704,13 +703,13 @@ public void testSecondFails() { s1.onNext("b"); s2.onError(new RuntimeException("Forced failure")); - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(subscriber, times(1)).onError(any(RuntimeException.class)); s2.onNext("1"); s2.onNext("2"); - inOrder.verify(observer, never()).onComplete(); - inOrder.verify(observer, never()).onNext(any(String.class)); + inOrder.verify(subscriber, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(any(String.class)); inOrder.verifyNoMoreInteractions(); } @@ -718,14 +717,14 @@ public void testSecondFails() { public void testStartWithOnCompletedTwice() { // issue: https://groups.google.com/forum/#!topic/rxjava/79cWTv3TFp0 // The problem is the original "zip" implementation does not wrap - // an internal observer with a SafeSubscriber. However, in the "zip", + // an internal subscriber with a SafeSubscriber. However, in the "zip", // it may calls "onComplete" twice. That breaks the Rx contract. // This test tries to emulate this case. // As "TestHelper.mockSubscriber()" will create an instance in the package "rx", - // we need to wrap "TestHelper.mockSubscriber()" with an observer instance + // we need to wrap "TestHelper.mockSubscriber()" with an subscriber instance // which is in the package "rx.operators". - final Subscriber<Integer> observer = TestHelper.mockSubscriber(); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); Flowable.zip(Flowable.just(1), Flowable.just(1), new BiFunction<Integer, Integer, Integer>() { @@ -737,24 +736,24 @@ public Integer apply(Integer a, Integer b) { @Override public void onComplete() { - observer.onComplete(); + subscriber.onComplete(); } @Override public void onError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override public void onNext(Integer args) { - observer.onNext(args); + subscriber.onNext(args); } }); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(2); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, times(1)).onNext(2); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -858,7 +857,7 @@ public void onNext(String s) { public void testEmitNull() { Flowable<Integer> oi = Flowable.just(1, null, 3); Flowable<String> os = Flowable.just("a", "b", null); - Flowable<String> o = Flowable.zip(oi, os, new BiFunction<Integer, String, String>() { + Flowable<String> f = Flowable.zip(oi, os, new BiFunction<Integer, String, String>() { @Override public String apply(Integer t1, String t2) { @@ -868,7 +867,7 @@ public String apply(Integer t1, String t2) { }); final ArrayList<String> list = new ArrayList<String>(); - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String s) { @@ -906,7 +905,7 @@ static String value(Notification notification) { public void testEmitMaterializedNotifications() { Flowable<Notification<Integer>> oi = Flowable.just(1, 2, 3).materialize(); Flowable<Notification<String>> os = Flowable.just("a", "b", "c").materialize(); - Flowable<String> o = Flowable.zip(oi, os, new BiFunction<Notification<Integer>, Notification<String>, String>() { + Flowable<String> f = Flowable.zip(oi, os, new BiFunction<Notification<Integer>, Notification<String>, String>() { @Override public String apply(Notification<Integer> t1, Notification<String> t2) { @@ -916,7 +915,7 @@ public String apply(Notification<Integer> t1, Notification<String> t2) { }); final ArrayList<String> list = new ArrayList<String>(); - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String s) { @@ -935,7 +934,7 @@ public void accept(String s) { @Test public void testStartEmptyFlowables() { - Flowable<String> o = Flowable.zip(Flowable.<Integer> empty(), Flowable.<String> empty(), new BiFunction<Integer, String, String>() { + Flowable<String> f = Flowable.zip(Flowable.<Integer> empty(), Flowable.<String> empty(), new BiFunction<Integer, String, String>() { @Override public String apply(Integer t1, String t2) { @@ -945,7 +944,7 @@ public String apply(Integer t1, String t2) { }); final ArrayList<String> list = new ArrayList<String>(); - o.subscribe(new Consumer<String>() { + f.subscribe(new Consumer<String>() { @Override public void accept(String s) { @@ -963,7 +962,7 @@ public void testStartEmptyList() { final Object invoked = new Object(); Collection<Flowable<Object>> observables = Collections.emptyList(); - Flowable<Object> o = Flowable.zip(observables, new Function<Object[], Object>() { + Flowable<Object> f = Flowable.zip(observables, new Function<Object[], Object>() { @Override public Object apply(final Object[] args) { assertEquals("No argument should have been passed", 0, args.length); @@ -972,7 +971,7 @@ public Object apply(final Object[] args) { }); TestSubscriber<Object> ts = new TestSubscriber<Object>(); - o.subscribe(ts); + f.subscribe(ts); ts.awaitTerminalEvent(200, TimeUnit.MILLISECONDS); ts.assertNoValues(); } @@ -987,7 +986,7 @@ public void testStartEmptyListBlocking() { final Object invoked = new Object(); Collection<Flowable<Object>> observables = Collections.emptyList(); - Flowable<Object> o = Flowable.zip(observables, new Function<Object[], Object>() { + Flowable<Object> f = Flowable.zip(observables, new Function<Object[], Object>() { @Override public Object apply(final Object[] args) { assertEquals("No argument should have been passed", 0, args.length); @@ -995,18 +994,18 @@ public Object apply(final Object[] args) { } }); - o.blockingLast(); + f.blockingLast(); } @Test public void testBackpressureSync() { AtomicInteger generatedA = new AtomicInteger(); AtomicInteger generatedB = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generatedA); - Flowable<Integer> o2 = createInfiniteFlowable(generatedB); + Flowable<Integer> f1 = createInfiniteFlowable(generatedA); + Flowable<Integer> f2 = createInfiniteFlowable(generatedB); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { + Flowable.zip(f1, f2, new BiFunction<Integer, Integer, String>() { @Override public String apply(Integer t1, Integer t2) { @@ -1026,11 +1025,11 @@ public String apply(Integer t1, Integer t2) { public void testBackpressureAsync() { AtomicInteger generatedA = new AtomicInteger(); AtomicInteger generatedB = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generatedA).subscribeOn(Schedulers.computation()); - Flowable<Integer> o2 = createInfiniteFlowable(generatedB).subscribeOn(Schedulers.computation()); + Flowable<Integer> f1 = createInfiniteFlowable(generatedA).subscribeOn(Schedulers.computation()); + Flowable<Integer> f2 = createInfiniteFlowable(generatedB).subscribeOn(Schedulers.computation()); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { + Flowable.zip(f1, f2, new BiFunction<Integer, Integer, String>() { @Override public String apply(Integer t1, Integer t2) { @@ -1050,11 +1049,11 @@ public String apply(Integer t1, Integer t2) { public void testDownstreamBackpressureRequestsWithFiniteSyncFlowables() { AtomicInteger generatedA = new AtomicInteger(); AtomicInteger generatedB = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generatedA).take(Flowable.bufferSize() * 2); - Flowable<Integer> o2 = createInfiniteFlowable(generatedB).take(Flowable.bufferSize() * 2); + Flowable<Integer> f1 = createInfiniteFlowable(generatedA).take(Flowable.bufferSize() * 2); + Flowable<Integer> f2 = createInfiniteFlowable(generatedB).take(Flowable.bufferSize() * 2); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { + Flowable.zip(f1, f2, new BiFunction<Integer, Integer, String>() { @Override public String apply(Integer t1, Integer t2) { @@ -1075,11 +1074,11 @@ public String apply(Integer t1, Integer t2) { public void testDownstreamBackpressureRequestsWithInfiniteAsyncFlowables() { AtomicInteger generatedA = new AtomicInteger(); AtomicInteger generatedB = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generatedA).subscribeOn(Schedulers.computation()); - Flowable<Integer> o2 = createInfiniteFlowable(generatedB).subscribeOn(Schedulers.computation()); + Flowable<Integer> f1 = createInfiniteFlowable(generatedA).subscribeOn(Schedulers.computation()); + Flowable<Integer> f2 = createInfiniteFlowable(generatedB).subscribeOn(Schedulers.computation()); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { + Flowable.zip(f1, f2, new BiFunction<Integer, Integer, String>() { @Override public String apply(Integer t1, Integer t2) { @@ -1100,11 +1099,11 @@ public String apply(Integer t1, Integer t2) { public void testDownstreamBackpressureRequestsWithInfiniteSyncFlowables() { AtomicInteger generatedA = new AtomicInteger(); AtomicInteger generatedB = new AtomicInteger(); - Flowable<Integer> o1 = createInfiniteFlowable(generatedA); - Flowable<Integer> o2 = createInfiniteFlowable(generatedB); + Flowable<Integer> f1 = createInfiniteFlowable(generatedA); + Flowable<Integer> f2 = createInfiniteFlowable(generatedB); TestSubscriber<String> ts = new TestSubscriber<String>(); - Flowable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { + Flowable.zip(f1, f2, new BiFunction<Integer, Integer, String>() { @Override public String apply(Integer t1, Integer t2) { @@ -1122,7 +1121,7 @@ public String apply(Integer t1, Integer t2) { } private Flowable<Integer> createInfiniteFlowable(final AtomicInteger generated) { - Flowable<Integer> observable = Flowable.fromIterable(new Iterable<Integer>() { + Flowable<Integer> flowable = Flowable.fromIterable(new Iterable<Integer>() { @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { @@ -1143,7 +1142,7 @@ public boolean hasNext() { }; } }); - return observable; + return flowable; } Flowable<Integer> OBSERVABLE_OF_5_INTEGERS = OBSERVABLE_OF_5_INTEGERS(new AtomicInteger()); @@ -1152,18 +1151,18 @@ Flowable<Integer> OBSERVABLE_OF_5_INTEGERS(final AtomicInteger numEmitted) { return Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> o) { + public void subscribe(final Subscriber<? super Integer> subscriber) { BooleanSubscription bs = new BooleanSubscription(); - o.onSubscribe(bs); + subscriber.onSubscribe(bs); for (int i = 1; i <= 5; i++) { if (bs.isCancelled()) { break; } numEmitted.incrementAndGet(); - o.onNext(i); + subscriber.onNext(i); Thread.yield(); } - o.onComplete(); + subscriber.onComplete(); } }); @@ -1173,9 +1172,9 @@ Flowable<Integer> ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(final CountDownLatch lat return Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> o) { + public void subscribe(final Subscriber<? super Integer> subscriber) { final BooleanSubscription bs = new BooleanSubscription(); - o.onSubscribe(bs); + subscriber.onSubscribe(bs); Thread t = new Thread(new Runnable() { @Override @@ -1184,10 +1183,10 @@ public void run() { System.out.println("Starting thread: " + Thread.currentThread()); int i = 1; while (!bs.isCancelled()) { - o.onNext(i++); + subscriber.onNext(i++); Thread.yield(); } - o.onComplete(); + subscriber.onComplete(); latch.countDown(); System.out.println("Ending thread: " + Thread.currentThread()); } @@ -1224,6 +1223,7 @@ public Integer apply(Integer i1, Integer i2) { } assertEquals(expected, zip2.toList().blockingGet()); } + @Test public void testUnboundedDownstreamOverrequesting() { Flowable<Integer> source = Flowable.range(1, 2).zipWith(Flowable.range(1, 2), new BiFunction<Integer, Integer, Integer>() { @@ -1247,6 +1247,7 @@ public void onNext(Integer t) { ts.assertTerminated(); ts.assertValues(11, 22); } + @Test(timeout = 10000) public void testZipRace() { long startTime = System.currentTimeMillis(); @@ -1570,6 +1571,7 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .test() .assertResult("12345678"); } + @Test public void zip9() { Flowable.zip(Flowable.just(1), @@ -1589,7 +1591,6 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .assertResult("123456789"); } - @Test public void zipArrayMany() { @SuppressWarnings("unchecked") @@ -1893,4 +1894,34 @@ public Integer apply(Integer a, Integer b) throws Exception { ts.assertResult(4); } + + @Test + public void firstErrorPreventsSecondSubscription() { + final AtomicInteger counter = new AtomicInteger(); + + List<Flowable<?>> flowableList = new ArrayList<Flowable<?>>(); + flowableList.add(Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> e) + throws Exception { throw new TestException(); } + }, BackpressureStrategy.MISSING)); + flowableList.add(Flowable.create(new FlowableOnSubscribe<Object>() { + @Override + public void subscribe(FlowableEmitter<Object> e) + throws Exception { counter.getAndIncrement(); } + }, BackpressureStrategy.MISSING)); + + Flowable.zip(flowableList, + new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }) + .test() + .assertFailure(TestException.class) + ; + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java b/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java index a03d91385f..1f29136e00 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/NotificationLiteTest.java @@ -23,7 +23,6 @@ import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.NotificationLite; - public class NotificationLiteTest { @Test diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java index de8120f227..a701a279ab 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeAmbTest.java @@ -14,16 +14,23 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; + import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.*; public class MaybeAmbTest { @@ -71,7 +78,7 @@ public void dispose() { @SuppressWarnings("unchecked") @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -96,7 +103,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -108,4 +115,142 @@ public void run() { } } } + + @Test + public void disposeNoFurtherSignals() { + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Maybe.ambArray(new Maybe<Integer>() { + @Override + protected void subscribeActual( + MaybeObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSuccess(1); + observer.onSuccess(2); + observer.onComplete(); + } + }, Maybe.<Integer>never()) + .test(); + + to.cancel(); + + to.assertResult(1); + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Maybe.ambArray( + Maybe.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Maybe.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @Test + public void nullSourceSuccessRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + + final Subject<Integer> ps = ReplaySubject.create(); + ps.onNext(1); + + @SuppressWarnings("unchecked") + final Maybe<Integer> source = Maybe.ambArray(ps.singleElement(), + Maybe.<Integer>never(), Maybe.<Integer>never(), null); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.test(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, NullPointerException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java index 041f3a88a7..bae05a46b4 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCacheTest.java @@ -24,7 +24,6 @@ import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class MaybeCacheTest { @@ -53,7 +52,6 @@ public void offlineError() { .assertFailure(TestException.class); } - @Test public void offlineComplete() { Maybe<Integer> source = Maybe.<Integer>empty().cache(); @@ -74,7 +72,7 @@ public void onlineSuccess() { assertNotNull(((MaybeCache<Integer>)source).source.get()); - TestObserver<Integer> ts = source.test(); + TestObserver<Integer> to = source.test(); assertNull(((MaybeCache<Integer>)source).source.get()); @@ -82,12 +80,12 @@ public void onlineSuccess() { source.test(true).assertEmpty(); - ts.assertEmpty(); + to.assertEmpty(); pp.onNext(1); pp.onComplete(); - ts.assertResult(1); + to.assertResult(1); source.test().assertResult(1); @@ -104,7 +102,7 @@ public void onlineError() { assertNotNull(((MaybeCache<Integer>)source).source.get()); - TestObserver<Integer> ts = source.test(); + TestObserver<Integer> to = source.test(); assertNull(((MaybeCache<Integer>)source).source.get()); @@ -112,11 +110,11 @@ public void onlineError() { source.test(true).assertEmpty(); - ts.assertEmpty(); + to.assertEmpty(); pp.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); source.test().assertFailure(TestException.class); @@ -133,7 +131,7 @@ public void onlineComplete() { assertNotNull(((MaybeCache<Integer>)source).source.get()); - TestObserver<Integer> ts = source.test(); + TestObserver<Integer> to = source.test(); assertNull(((MaybeCache<Integer>)source).source.get()); @@ -141,11 +139,11 @@ public void onlineComplete() { source.test(true).assertEmpty(); - ts.assertEmpty(); + to.assertEmpty(); pp.onComplete(); - ts.assertResult(); + to.assertResult(); source.test().assertResult(); @@ -224,7 +222,7 @@ public void run() throws Exception { @Test public void addAddRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishProcessor<Integer> pp = PublishProcessor.create(); final Maybe<Integer> source = pp.singleElement().cache(); @@ -236,35 +234,35 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @Test public void removeRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishProcessor<Integer> pp = PublishProcessor.create(); final Maybe<Integer> source = pp.singleElement().cache(); - final TestObserver<Integer> ts1 = source.test(); - final TestObserver<Integer> ts2 = source.test(); + final TestObserver<Integer> to1 = source.test(); + final TestObserver<Integer> to2 = source.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ts2.cancel(); + to2.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserverTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserverTest.java index 5a85c2f69d..e810f63e74 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserverTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserverTest.java @@ -13,18 +13,17 @@ package io.reactivex.internal.operators.maybe; -import static org.junit.Assert.*; - -import java.util.List; - -import org.junit.Test; - import io.reactivex.TestHelper; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.plugins.RxJavaPlugins; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; public class MaybeCallbackObserverTest { @@ -121,4 +120,22 @@ public void run() throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void onErrorMissingShouldReportNoCustomOnError() { + MaybeCallbackObserver<Integer> o = new MaybeCallbackObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION); + + assertFalse(o.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + MaybeCallbackObserver<Integer> o = new MaybeCallbackObserver<Integer>(Functions.<Integer>emptyConsumer(), + Functions.<Throwable>emptyConsumer(), + Functions.EMPTY_ACTION); + + assertTrue(o.hasCustomOnError()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java index 04773bcedf..fb14e27a19 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayTest.java @@ -13,17 +13,17 @@ package io.reactivex.internal.operators.maybe; +import static org.junit.Assert.assertEquals; + import java.io.IOException; import java.util.List; -import static org.junit.Assert.*; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class MaybeConcatArrayTest { @@ -83,7 +83,7 @@ public void backpressureDelayError() { @SuppressWarnings("unchecked") @Test public void requestCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = Maybe.concatArray(Maybe.just(1), Maybe.just(2)) .test(0L); @@ -101,14 +101,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @SuppressWarnings("unchecked") @Test public void requestCancelRaceDelayError() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = Maybe.concatArrayDelayError(Maybe.just(1), Maybe.just(2)) .test(0L); @@ -126,7 +126,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -151,7 +151,6 @@ protected void subscribeActual(MaybeObserver<? super Integer> observer) { o[0].onError(new TestException()); - TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { RxJavaPlugins.reset(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatIterableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatIterableTest.java index 4bc41e8af7..1ae2ac56bb 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeConcatIterableTest.java @@ -24,7 +24,6 @@ import io.reactivex.functions.Function; import io.reactivex.internal.util.CrashingMappedIterable; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class MaybeConcatIterableTest { @@ -61,11 +60,11 @@ public void error() { @SuppressWarnings("unchecked") @Test public void successCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestSubscriber<Integer> to = Maybe.concat(Arrays.asList(pp.singleElement())) + final TestSubscriber<Integer> ts = Maybe.concat(Arrays.asList(pp.singleElement())) .test(); pp.onNext(1); @@ -73,7 +72,7 @@ public void successCancelRace() { Runnable r1 = new Runnable() { @Override public void run() { - to.cancel(); + ts.cancel(); } }; @@ -84,7 +83,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java index e1172ce5d7..60f319f873 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeContainsTest.java @@ -50,16 +50,15 @@ public void error() { public void dispose() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Boolean> ts = pp.singleElement().contains(1).test(); + TestObserver<Boolean> to = pp.singleElement().contains(1).test(); assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } - @Test public void isDisposed() { PublishProcessor<Integer> pp = PublishProcessor.create(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCountTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCountTest.java index be5e117214..3a0d29ab9c 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCountTest.java @@ -45,11 +45,11 @@ public void error() { public void dispose() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Long> ts = pp.singleElement().count().test(); + TestObserver<Long> to = pp.singleElement().count().test(); assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCreateTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCreateTest.java index 477562310b..1e114a2ac3 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeCreateTest.java @@ -335,4 +335,14 @@ public void subscribe(MaybeEmitter<Object> e) throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void emitterHasToString() { + Maybe.create(new MaybeOnSubscribe<Object>() { + @Override + public void subscribe(MaybeEmitter<Object> emitter) throws Exception { + assertTrue(emitter.toString().contains(MaybeCreate.Emitter.class.getSimpleName())); + } + }).test().assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java index 086db5388f..4719ac8977 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherTest.java @@ -18,10 +18,12 @@ import java.util.List; import org.junit.Test; +import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; @@ -31,10 +33,10 @@ public class MaybeDelayOtherTest { public void justWithOnNext() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -42,17 +44,17 @@ public void justWithOnNext() { assertFalse(pp.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @Test public void justWithOnComplete() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -60,18 +62,17 @@ public void justWithOnComplete() { assertFalse(pp.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } - @Test public void justWithOnError() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -79,17 +80,17 @@ public void justWithOnError() { assertFalse(pp.hasSubscribers()); - ts.assertFailureAndMessage(TestException.class, "Other"); + to.assertFailureAndMessage(TestException.class, "Other"); } @Test public void emptyWithOnNext() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>empty() + TestObserver<Integer> to = Maybe.<Integer>empty() .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -97,18 +98,17 @@ public void emptyWithOnNext() { assertFalse(pp.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } - @Test public void emptyWithOnComplete() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>empty() + TestObserver<Integer> to = Maybe.<Integer>empty() .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -116,17 +116,17 @@ public void emptyWithOnComplete() { assertFalse(pp.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } @Test public void emptyWithOnError() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>empty() + TestObserver<Integer> to = Maybe.<Integer>empty() .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -134,17 +134,17 @@ public void emptyWithOnError() { assertFalse(pp.hasSubscribers()); - ts.assertFailureAndMessage(TestException.class, "Other"); + to.assertFailureAndMessage(TestException.class, "Other"); } @Test public void errorWithOnNext() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>error(new TestException("Main")) + TestObserver<Integer> to = Maybe.<Integer>error(new TestException("Main")) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -152,17 +152,17 @@ public void errorWithOnNext() { assertFalse(pp.hasSubscribers()); - ts.assertFailureAndMessage(TestException.class, "Main"); + to.assertFailureAndMessage(TestException.class, "Main"); } @Test public void errorWithOnComplete() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>error(new TestException("Main")) + TestObserver<Integer> to = Maybe.<Integer>error(new TestException("Main")) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -170,17 +170,17 @@ public void errorWithOnComplete() { assertFalse(pp.hasSubscribers()); - ts.assertFailureAndMessage(TestException.class, "Main"); + to.assertFailureAndMessage(TestException.class, "Main"); } @Test public void errorWithOnError() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.<Integer>error(new TestException("Main")) + TestObserver<Integer> to = Maybe.<Integer>error(new TestException("Main")) .delay(pp).test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp.hasSubscribers()); @@ -188,9 +188,9 @@ public void errorWithOnError() { assertFalse(pp.hasSubscribers()); - ts.assertFailure(CompositeException.class); + to.assertFailure(CompositeException.class); - List<Throwable> list = TestHelper.compositeList(ts.errors().get(0)); + List<Throwable> list = TestHelper.compositeList(to.errors().get(0)); assertEquals(2, list.size()); TestHelper.assertError(list, 0, TestException.class, "Main"); @@ -216,4 +216,28 @@ public MaybeSource<Integer> apply(Completable c) throws Exception { public void withOtherPublisherDispose() { TestHelper.checkDisposed(Maybe.just(1).delay(Flowable.just(1))); } + + @Test + public void withOtherPublisherDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybe(new Function<Maybe<Integer>, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Maybe<Integer> c) throws Exception { + return c.delay(Flowable.never()); + } + }); + } + + @Test + public void otherPublisherNextSlipsThrough() { + Maybe.just(1).delay(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + } + }) + .test() + .assertResult(1); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionTest.java index a684ff68ff..e1f7886cb3 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionTest.java @@ -36,18 +36,18 @@ public class MaybeDelaySubscriptionTest { public void normal() { PublishProcessor<Object> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.just(1).delaySubscription(pp) + TestObserver<Integer> to = Maybe.just(1).delaySubscription(pp) .test(); assertTrue(pp.hasSubscribers()); - ts.assertEmpty(); + to.assertEmpty(); pp.onNext("one"); assertFalse(pp.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -70,19 +70,19 @@ public void timedEmpty() { public void timedTestScheduler() { TestScheduler scheduler = new TestScheduler(); - TestObserver<Integer> ts = Maybe.just(1) + TestObserver<Integer> to = Maybe.just(1) .delaySubscription(100, TimeUnit.MILLISECONDS, scheduler) .test(); - ts.assertEmpty(); + to.assertEmpty(); scheduler.advanceTimeBy(99, TimeUnit.MILLISECONDS); - ts.assertEmpty(); + to.assertEmpty(); scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -122,12 +122,12 @@ public void withPublisherCallAfterTerminalEvent() { try { Flowable<Integer> f = new Flowable<Integer>() { @Override - protected void subscribeActual(Subscriber<? super Integer> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext(1); - observer.onError(new TestException()); - observer.onComplete(); - observer.onNext(2); + protected void subscribeActual(Subscriber<? super Integer> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onError(new TestException()); + subscriber.onComplete(); + subscriber.onNext(2); } }; diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayTest.java index a392f5f11f..c6b9fdbd4c 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDelayTest.java @@ -66,25 +66,25 @@ public void nullScheduler() { public void disposeDuringDelay() { TestScheduler scheduler = new TestScheduler(); - TestObserver<Integer> ts = Maybe.just(1).delay(100, TimeUnit.MILLISECONDS, scheduler) + TestObserver<Integer> to = Maybe.just(1).delay(100, TimeUnit.MILLISECONDS, scheduler) .test(); - ts.cancel(); + to.cancel(); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ts.assertEmpty(); + to.assertEmpty(); } @Test public void dispose() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Integer> ts = pp.singleElement().delay(100, TimeUnit.MILLISECONDS).test(); + TestObserver<Integer> to = pp.singleElement().delay(100, TimeUnit.MILLISECONDS).test(); assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDetachTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDetachTest.java index 7022339ed3..d1a28f2c47 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDetachTest.java @@ -13,11 +13,18 @@ package io.reactivex.internal.operators.maybe; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.lang.ref.WeakReference; + import org.junit.Test; import io.reactivex.*; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; public class MaybeDetachTest { @@ -53,4 +60,108 @@ public void onComplete() { .test() .assertResult(); } + + @Test + public void cancelDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Object> to = new Maybe<Object>() { + @Override + protected void subscribeActual(MaybeObserver<? super Object> observer) { + observer.onSubscribe(wr.get()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + to.cancel(); + + System.gc(); + Thread.sleep(200); + + to.assertEmpty(); + + assertNull(wr.get()); + } + + @Test + public void completeDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Integer> to = new Maybe<Integer>() { + @Override + protected void subscribeActual(MaybeObserver<? super Integer> observer) { + observer.onSubscribe(wr.get()); + observer.onComplete(); + observer.onComplete(); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertResult(); + + assertNull(wr.get()); + } + + @Test + public void errorDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Integer> to = new Maybe<Integer>() { + @Override + protected void subscribeActual(MaybeObserver<? super Integer> observer) { + observer.onSubscribe(wr.get()); + observer.onError(new TestException()); + observer.onError(new IOException()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertFailure(TestException.class); + + assertNull(wr.get()); + } + + @Test + public void successDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Integer> to = new Maybe<Integer>() { + @Override + protected void subscribeActual(MaybeObserver<? super Integer> observer) { + observer.onSubscribe(wr.get()); + observer.onSuccess(1); + observer.onSuccess(2); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertResult(1); + + assertNull(wr.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccessTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccessTest.java index cee3a213f2..31e046c41f 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccessTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccessTest.java @@ -38,7 +38,7 @@ public void accept(Integer e) throws Exception { } }; - final TestObserver<Integer> ts = new TestObserver<Integer>() { + final TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -50,7 +50,7 @@ public void onNext(Integer t) { public void just() { Maybe.just(1) .doAfterSuccess(afterSuccess) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -60,7 +60,7 @@ public void just() { public void error() { Maybe.<Integer>error(new TestException()) .doAfterSuccess(afterSuccess) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); @@ -70,7 +70,7 @@ public void error() { public void empty() { Maybe.<Integer>empty() .doAfterSuccess(afterSuccess) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(); assertTrue(values.isEmpty()); @@ -86,7 +86,7 @@ public void justConditional() { Maybe.just(1) .doAfterSuccess(afterSuccess) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -97,7 +97,7 @@ public void errorConditional() { Maybe.<Integer>error(new TestException()) .doAfterSuccess(afterSuccess) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); @@ -108,7 +108,7 @@ public void emptyConditional() { Maybe.<Integer>empty() .doAfterSuccess(afterSuccess) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(); assertTrue(values.isEmpty()); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnEventTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnEventTest.java index 6df94cbd82..19389db622 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnEventTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnEventTest.java @@ -61,16 +61,16 @@ public void onSubscribeCrash() { new Maybe<Integer>() { @Override - protected void subscribeActual(MaybeObserver<? super Integer> s) { - s.onSubscribe(bs); - s.onError(new TestException("Second")); - s.onComplete(); - s.onSuccess(1); + protected void subscribeActual(MaybeObserver<? super Integer> observer) { + observer.onSubscribe(bs); + observer.onError(new TestException("Second")); + observer.onComplete(); + observer.onSuccess(1); } } .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException("First"); } }) diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java new file mode 100644 index 0000000000..0e90e57731 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import io.reactivex.Maybe; +import io.reactivex.TestHelper; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +public class MaybeDoOnTerminateTest { + + @Test(expected = NullPointerException.class) + public void doOnTerminate() { + Maybe.just(1).doOnTerminate(null); + } + + @Test + public void doOnTerminateSuccess() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.just(1).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(1); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateError() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.error(new TestException()).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateComplete() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Maybe.empty().doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateSuccessCrash() { + Maybe.just(1).doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doOnTerminateErrorCrash() { + TestObserver<Object> to = Maybe.error(new TestException("Outer")) + .doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } + + @Test + public void doOnTerminateCompleteCrash() { + Maybe.empty() + .doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelectorTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelectorTest.java index a618ddc94a..f817bfa839 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelectorTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelectorTest.java @@ -20,6 +20,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; public class MaybeFlatMapBiSelectorTest { @@ -210,4 +211,25 @@ public Object apply(Integer a, Integer b) throws Exception { .test() .assertFailure(NullPointerException.class); } + + @Test + public void mapperCancels() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Maybe.just(1) + .flatMap(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) throws Exception { + to.cancel(); + return Maybe.just(2); + } + }, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + throw new IllegalStateException(); + } + }) + .subscribeWith(to) + .assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowableTest.java index 60b270ec28..450129d175 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowableTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.reactivestreams.*; +import org.reactivestreams.Subscription; import io.reactivex.*; import io.reactivex.exceptions.TestException; @@ -121,7 +121,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { @Test public void fused() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.just(1).flattenAsFlowable(new Function<Integer, Iterable<Integer>>() { @Override @@ -129,17 +129,17 @@ public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(v, v + 1); } }) - .subscribe(to); + .subscribe(ts); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2); ; } @Test public void fusedNoSync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Maybe.just(1).flattenAsFlowable(new Function<Integer, Iterable<Integer>>() { @Override @@ -147,10 +147,10 @@ public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(v, v + 1); } }) - .subscribe(to); + .subscribe(ts); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.NONE)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2); ; } @@ -299,24 +299,24 @@ public Iterable<Integer> apply(Object v) throws Exception { return Arrays.asList(1, 2, 3); } }).subscribe(new FlowableSubscriber<Integer>() { - QueueSubscription<Integer> qd; + QueueSubscription<Integer> qs; @SuppressWarnings("unchecked") @Override - public void onSubscribe(Subscription d) { - qd = (QueueSubscription<Integer>)d; + public void onSubscribe(Subscription s) { + qs = (QueueSubscription<Integer>)s; - assertEquals(QueueSubscription.ASYNC, qd.requestFusion(QueueSubscription.ANY)); + assertEquals(QueueFuseable.ASYNC, qs.requestFusion(QueueFuseable.ANY)); } @Override public void onNext(Integer value) { - assertFalse(qd.isEmpty()); + assertFalse(qs.isEmpty()); - qd.clear(); + qs.clear(); - assertTrue(qd.isEmpty()); + assertTrue(qs.isEmpty()); - qd.cancel(); + qs.cancel(); } @Override @@ -403,7 +403,7 @@ public void requestCreateInnerRace() { final Integer[] a = new Integer[1000]; Arrays.fill(a, 1); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); ps.onNext(1); @@ -436,13 +436,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void cancelCreateInnerRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); ps.onNext(1); @@ -470,7 +470,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservableTest.java index 5da9082918..c4648d8331 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservableTest.java @@ -13,10 +13,11 @@ package io.reactivex.internal.operators.maybe; +import static org.junit.Assert.*; + import java.util.*; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; import org.junit.Test; import io.reactivex.*; @@ -24,7 +25,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.util.CrashingIterable; import io.reactivex.observers.*; import io.reactivex.schedulers.Schedulers; @@ -98,7 +99,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { @Test public void fused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Maybe.just(1).flattenAsObservable(new Function<Integer, Iterable<Integer>>() { @Override @@ -109,14 +110,14 @@ public Iterable<Integer> apply(Integer v) throws Exception { .subscribe(to); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2); ; } @Test public void fusedNoSync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Maybe.just(1).flattenAsObservable(new Function<Integer, Iterable<Integer>>() { @Override @@ -127,7 +128,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { .subscribe(to); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.NONE)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2); ; } @@ -307,7 +308,7 @@ public Iterable<Integer> apply(Object v) throws Exception { public void onSubscribe(Disposable d) { qd = (QueueDisposable<Integer>)d; - assertEquals(QueueDisposable.ASYNC, qd.requestFusion(QueueDisposable.ANY)); + assertEquals(QueueFuseable.ASYNC, qd.requestFusion(QueueFuseable.ANY)); } @Override diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java index a8e3d18c22..6e7c1b242f 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromActionTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.*; @@ -135,7 +136,7 @@ public void noErrorLoss() throws Exception { @Override public void run() throws Exception { cdl1.countDown(); - cdl2.await(); + cdl2.await(5, TimeUnit.SECONDS); } }).subscribeOn(Schedulers.single()).test(); @@ -143,8 +144,6 @@ public void run() throws Exception { to.cancel(); - cdl2.countDown(); - int timeout = 10; while (timeout-- > 0 && errors.isEmpty()) { @@ -156,4 +155,31 @@ public void run() throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void disposedUpfront() throws Exception { + Action run = mock(Action.class); + + Maybe.fromAction(run) + .test(true) + .assertEmpty(); + + verify(run, never()).run(); + } + + @Test + public void cancelWhileRunning() { + final TestObserver<Object> to = new TestObserver<Object>(); + + Maybe.fromAction(new Action() { + @Override + public void run() throws Exception { + to.dispose(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java index 71e2061693..44f50947ee 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java @@ -14,17 +14,21 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import io.reactivex.disposables.Disposable; import org.junit.Test; import io.reactivex.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; public class MaybeFromCallableTest { @Test(expected = NullPointerException.class) @@ -138,7 +142,7 @@ public void noErrorLoss() throws Exception { @Override public Integer call() throws Exception { cdl1.countDown(); - cdl2.await(); + cdl2.await(5, TimeUnit.SECONDS); return 1; } }).subscribeOn(Schedulers.single()).test(); @@ -147,8 +151,6 @@ public Integer call() throws Exception { to.cancel(); - cdl2.countDown(); - int timeout = 10; while (timeout-- > 0 && errors.isEmpty()) { @@ -160,4 +162,57 @@ public Integer call() throws Exception { RxJavaPlugins.reset(); } } + + @SuppressWarnings("unchecked") + @Test + public void shouldNotDeliverResultIfSubscriberUnsubscribedBeforeEmission() throws Exception { + Callable<String> func = mock(Callable.class); + + final CountDownLatch funcLatch = new CountDownLatch(1); + final CountDownLatch observerLatch = new CountDownLatch(1); + + when(func.call()).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + observerLatch.countDown(); + + try { + funcLatch.await(); + } catch (InterruptedException e) { + // It's okay, unsubscription causes Thread interruption + + // Restoring interruption status of the Thread + Thread.currentThread().interrupt(); + } + + return "should_not_be_delivered"; + } + }); + + Maybe<String> fromCallableObservable = Maybe.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + TestObserver<String> outer = new TestObserver<String>(observer); + + fromCallableObservable + .subscribeOn(Schedulers.computation()) + .subscribe(outer); + + // Wait until func will be invoked + observerLatch.await(); + + // Unsubscribing before emission + outer.cancel(); + + // Emitting result + funcLatch.countDown(); + + // func must be invoked + verify(func).call(); + + // Observer must not be notified at all + verify(observer).onSubscribe(any(Disposable.class)); + verifyNoMoreInteractions(observer); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java index 1b511dd515..3086eb6c57 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCompletableTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.maybe; - import io.reactivex.*; import io.reactivex.functions.Function; import io.reactivex.internal.fuseable.HasUpstreamCompletableSource; diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java index 4f3ab7f571..d08b93a50a 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromFutureTest.java @@ -13,12 +13,17 @@ package io.reactivex.internal.operators.maybe; +import static org.junit.Assert.assertTrue; + import java.util.concurrent.*; import org.junit.Test; import io.reactivex.Maybe; +import io.reactivex.exceptions.TestException; import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; public class MaybeFromFutureTest { @@ -58,4 +63,60 @@ public void interrupt() { Maybe.fromFuture(ft, 1, TimeUnit.MILLISECONDS).test() .assertFailure(InterruptedException.class); } + + @Test + public void cancelWhileRunning() { + final TestObserver<Object> to = new TestObserver<Object>(); + + FutureTask<Object> ft = new FutureTask<Object>(new Runnable() { + @Override + public void run() { + to.cancel(); + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void cancelAndCrashWhileRunning() { + final TestObserver<Object> to = new TestObserver<Object>(); + + FutureTask<Object> ft = new FutureTask<Object>(new Runnable() { + @Override + public void run() { + to.cancel(); + throw new TestException(); + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void futureNull() { + FutureTask<Object> ft = new FutureTask<Object>(new Runnable() { + @Override + public void run() { + } + }, null); + + Schedulers.single().scheduleDirect(ft, 100, TimeUnit.MILLISECONDS); + + Maybe.fromFuture(ft) + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java index 339ab5018e..08d0bbc4b3 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromRunnableTest.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.*; @@ -134,7 +135,7 @@ public void noErrorLoss() throws Exception { public void run() { cdl1.countDown(); try { - cdl2.await(); + cdl2.await(5, TimeUnit.SECONDS); } catch (InterruptedException ex) { throw new RuntimeException(ex); } @@ -145,8 +146,6 @@ public void run() { to.cancel(); - cdl2.countDown(); - int timeout = 10; while (timeout-- > 0 && errors.isEmpty()) { @@ -160,4 +159,31 @@ public void run() { RxJavaPlugins.reset(); } } + + @Test + public void disposedUpfront() { + Runnable run = mock(Runnable.class); + + Maybe.fromRunnable(run) + .test(true) + .assertEmpty(); + + verify(run, never()).run(); + } + + @Test + public void cancelWhileRunning() { + final TestObserver<Object> to = new TestObserver<Object>(); + + Maybe.fromRunnable(new Runnable() { + @Override + public void run() { + to.dispose(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java index 3efe9dbec3..3602d44aa0 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeIsEmptyTest.java @@ -54,7 +54,6 @@ public void fusedBackToMaybe() { .toMaybe() instanceof MaybeIsEmpty); } - @Test public void normalToMaybe() { Maybe.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java new file mode 100644 index 0000000000..393c170e1b --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMapTest.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; + +public class MaybeMapTest { + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybe(new Function<Maybe<Object>, MaybeSource<Object>>() { + @Override + public MaybeSource<Object> apply(Maybe<Object> m) throws Exception { + return m.map(Functions.identity()); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java new file mode 100644 index 0000000000..f429ecddf2 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMaterializeTest.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.MaybeSubject; + +public class MaybeMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void success() { + Maybe.just(1) + .materialize() + .test() + .assertResult(Notification.createOnNext(1)); + } + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Maybe.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + @SuppressWarnings("unchecked") + public void empty() { + Maybe.empty() + .materialize() + .test() + .assertResult(Notification.createOnComplete()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybeToSingle(new Function<Maybe<Object>, SingleSource<Notification<Object>>>() { + @Override + public SingleSource<Notification<Object>> apply(Maybe<Object> v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(MaybeSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeArrayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeArrayTest.java index 61dc02da35..38ce04fdf0 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeArrayTest.java @@ -18,15 +18,14 @@ import java.util.*; import org.junit.Test; -import org.reactivestreams.*; +import org.reactivestreams.Subscription; import io.reactivex.*; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.maybe.MaybeMergeArray.MergeMaybeObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; import io.reactivex.subscribers.*; @@ -35,26 +34,26 @@ public class MaybeMergeArrayTest { @SuppressWarnings("unchecked") @Test public void normal() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Maybe.mergeArray(Maybe.just(1), Maybe.just(2)) .subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2); } @SuppressWarnings("unchecked") @Test public void fusedPollMixed() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.mergeArray(Maybe.just(1), Maybe.<Integer>empty(), Maybe.just(2)) .subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2); } @@ -63,23 +62,23 @@ public void fusedPollMixed() { public void fusedEmptyCheck() { Maybe.mergeArray(Maybe.just(1), Maybe.<Integer>empty(), Maybe.just(2)) .subscribe(new FlowableSubscriber<Integer>() { - QueueSubscription<Integer> qd; + QueueSubscription<Integer> qs; @Override - public void onSubscribe(Subscription d) { - qd = (QueueSubscription<Integer>)d; + public void onSubscribe(Subscription s) { + qs = (QueueSubscription<Integer>)s; - assertEquals(QueueSubscription.ASYNC, qd.requestFusion(QueueSubscription.ANY)); + assertEquals(QueueFuseable.ASYNC, qs.requestFusion(QueueFuseable.ANY)); } @Override public void onNext(Integer value) { - assertFalse(qd.isEmpty()); + assertFalse(qs.isEmpty()); - qd.clear(); + qs.clear(); - assertTrue(qd.isEmpty()); + assertTrue(qs.isEmpty()); - qd.cancel(); + qs.cancel(); } @Override @@ -120,20 +119,20 @@ public void firstErrors() { @SuppressWarnings("unchecked") @Test public void errorFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.mergeArray(Maybe.<Integer>error(new TestException()), Maybe.just(2)) .subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertFailure(TestException.class); } @SuppressWarnings("unchecked") @Test public void errorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -159,7 +158,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertFailure(Throwable.class); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeTest.java new file mode 100644 index 0000000000..176ae9c793 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeMergeTest.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.schedulers.Schedulers; + +public class MaybeMergeTest { + + @Test + public void delayErrorWithMaxConcurrency() { + Maybe.mergeDelayError( + Flowable.just(Maybe.just(1), Maybe.just(2), Maybe.just(3)), 1) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void delayErrorWithMaxConcurrencyError() { + Maybe.mergeDelayError( + Flowable.just(Maybe.just(1), Maybe.<Integer>error(new TestException()), Maybe.just(3)), 1) + .test() + .assertFailure(TestException.class, 1, 3); + } + + @Test + public void delayErrorWithMaxConcurrencyAsync() { + final AtomicInteger count = new AtomicInteger(); + @SuppressWarnings("unchecked") + Maybe<Integer>[] sources = new Maybe[3]; + for (int i = 0; i < 3; i++) { + final int j = i + 1; + sources[i] = Maybe.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return count.incrementAndGet() - j; + } + }) + .subscribeOn(Schedulers.io()); + } + + for (int i = 0; i < 1000; i++) { + count.set(0); + Maybe.mergeDelayError( + Flowable.fromArray(sources), 1) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(0, 0, 0); + } + } + + @Test + public void delayErrorWithMaxConcurrencyAsyncError() { + final AtomicInteger count = new AtomicInteger(); + @SuppressWarnings("unchecked") + Maybe<Integer>[] sources = new Maybe[3]; + for (int i = 0; i < 3; i++) { + final int j = i + 1; + sources[i] = Maybe.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return count.incrementAndGet() - j; + } + }) + .subscribeOn(Schedulers.io()); + } + sources[1] = Maybe.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + throw new TestException("" + count.incrementAndGet()); + } + }) + .subscribeOn(Schedulers.io()); + + for (int i = 0; i < 1000; i++) { + count.set(0); + Maybe.mergeDelayError( + Flowable.fromArray(sources), 1) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailureAndMessage(TestException.class, "2", 0, 0); + } + } + + @Test + public void scalar() { + Maybe.mergeDelayError( + Flowable.just(Maybe.just(1))) + .test() + .assertResult(1); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeOfTypeTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeOfTypeTest.java index ba93e61c6b..814a0c8e16 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeOfTypeTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeOfTypeTest.java @@ -32,38 +32,38 @@ public void normal() { @Test public void normalDowncast() { - TestObserver<Number> ts = Maybe.just(1) + TestObserver<Number> to = Maybe.just(1) .ofType(Number.class) .test(); // don't make this fluent, target type required! - ts.assertResult((Number)1); + to.assertResult((Number)1); } @Test public void notInstance() { - TestObserver<String> ts = Maybe.just(1) + TestObserver<String> to = Maybe.just(1) .ofType(String.class) .test(); // don't make this fluent, target type required! - ts.assertResult(); + to.assertResult(); } @Test public void error() { - TestObserver<Number> ts = Maybe.<Integer>error(new TestException()) + TestObserver<Number> to = Maybe.<Integer>error(new TestException()) .ofType(Number.class) .test(); // don't make this fluent, target type required! - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void errorNotInstance() { - TestObserver<String> ts = Maybe.<Integer>error(new TestException()) + TestObserver<String> to = Maybe.<Integer>error(new TestException()) .ofType(String.class) .test(); // don't make this fluent, target type required! - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java new file mode 100644 index 0000000000..2c242fc1a5 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingleTest.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.maybe; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; +import io.reactivex.observers.TestObserver; +import io.reactivex.processors.PublishProcessor; + +public class MaybeSwitchIfEmptySingleTest { + + @Test + public void nonEmpty() { + Maybe.just(1).switchIfEmpty(Single.just(2)).test().assertResult(1); + } + + @Test + public void empty() { + Maybe.<Integer>empty().switchIfEmpty(Single.just(2)).test().assertResult(2); + } + + @Test + public void error() { + Maybe.<Integer>error(new TestException()).switchIfEmpty(Single.just(2)) + .test().assertFailure(TestException.class); + } + + @Test + public void errorOther() { + Maybe.empty().switchIfEmpty(Single.<Integer>error(new TestException())) + .test().assertFailure(TestException.class); + } + + @Test + public void dispose() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestObserver<Integer> to = pp.singleElement().switchIfEmpty(Single.just(2)).test(); + + assertTrue(pp.hasSubscribers()); + + to.cancel(); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void isDisposed() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestHelper.checkDisposed(pp.singleElement().switchIfEmpty(Single.just(2))); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybeToSingle(new Function<Maybe<Integer>, Single<Integer>>() { + @Override + public Single<Integer> apply(Maybe<Integer> f) throws Exception { + return f.switchIfEmpty(Single.just(2)); + } + }); + } + + @Test + public void emptyCancelRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestObserver<Integer> to = pp.singleElement().switchIfEmpty(Single.just(2)).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @SuppressWarnings("rawtypes") + @Test + public void source() { + assertSame(Maybe.empty(), + ((HasUpstreamMaybeSource)(Maybe.<Integer>empty().switchIfEmpty(Single.just(1)))).source() + ); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java index 91ebad9948..142943ba67 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptyTest.java @@ -22,7 +22,6 @@ import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class MaybeSwitchIfEmptyTest { @@ -68,16 +67,15 @@ public void emptyOtherToo() { public void dispose() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Integer> ts = pp.singleElement().switchIfEmpty(Maybe.just(2)).test(); + TestObserver<Integer> to = pp.singleElement().switchIfEmpty(Maybe.just(2)).test(); assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } - @Test public void isDisposed() { PublishProcessor<Integer> pp = PublishProcessor.create(); @@ -97,10 +95,10 @@ public Maybe<Integer> apply(Maybe<Integer> f) throws Exception { @Test public void emptyCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); - final TestObserver<Integer> ts = pp.singleElement().switchIfEmpty(Maybe.just(2)).test(); + final TestObserver<Integer> to = pp.singleElement().switchIfEmpty(Maybe.just(2)).test(); Runnable r1 = new Runnable() { @Override @@ -112,11 +110,11 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisherTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisherTest.java index c1c49886f3..28bc00a1a2 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisherTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisherTest.java @@ -25,7 +25,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class MaybeTakeUntilPublisherTest { @@ -118,7 +117,7 @@ public void otherCompletes() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -143,7 +142,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -160,7 +159,7 @@ public void run() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -179,7 +178,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertResult(); } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilTest.java index 3fd457172b..8cd569869c 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilTest.java @@ -25,7 +25,7 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.MaybeSubject; public class MaybeTakeUntilTest { @@ -146,7 +146,7 @@ public void otherCompletes() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -171,7 +171,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -188,7 +188,7 @@ public void run() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -207,9 +207,261 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertResult(); } } + + @Test + public void untilMaybeMainSuccess() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(1); + } + + @Test + public void untilMaybeMainComplete() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilMaybeMainError() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilMaybeOtherSuccess() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilMaybeOtherComplete() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilMaybeOtherError() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilMaybeDispose() { + MaybeSubject<Integer> main = MaybeSubject.create(); + MaybeSubject<Integer> other = MaybeSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertEmpty(); + } + + @Test + public void untilPublisherMainSuccess() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + main.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertResult(1); + } + + @Test + public void untilPublisherMainComplete() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + main.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertResult(); + } + + @Test + public void untilPublisherMainError() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherOtherOnNext() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onNext(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertResult(); + } + + @Test + public void untilPublisherOtherOnComplete() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertResult(); + } + + @Test + public void untilPublisherOtherError() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherDispose() { + MaybeSubject<Integer> main = MaybeSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisherTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisherTest.java index 6fbb5f4359..763af24904 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisherTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisherTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.*; -import java.util.concurrent.*; +import java.util.concurrent.TimeoutException; import org.junit.Test; @@ -25,7 +25,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.*; public class MaybeTimeoutPublisherTest { @@ -157,7 +156,7 @@ public void dispose2() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); @@ -180,7 +179,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); } finally { @@ -191,7 +190,7 @@ public void run() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -210,7 +209,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertSubscribed().assertNoValues(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutTest.java index bf47c09cec..eaa5ef25f6 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimeoutTest.java @@ -276,7 +276,7 @@ public void dispose2() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); @@ -299,7 +299,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); } finally { @@ -310,7 +310,7 @@ public void run() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); @@ -329,7 +329,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertSubscribed().assertNoValues(); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimerTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimerTest.java index d588be655b..05d33d7175 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeTimerTest.java @@ -38,7 +38,7 @@ public void timerInterruptible() throws Exception { try { for (Scheduler s : new Scheduler[] { Schedulers.single(), Schedulers.computation(), Schedulers.newThread(), Schedulers.io(), Schedulers.from(exec) }) { final AtomicBoolean interrupted = new AtomicBoolean(); - TestObserver<Long> ts = Maybe.timer(1, TimeUnit.MILLISECONDS, s) + TestObserver<Long> to = Maybe.timer(1, TimeUnit.MILLISECONDS, s) .map(new Function<Long, Long>() { @Override public Long apply(Long v) throws Exception { @@ -54,7 +54,7 @@ public Long apply(Long v) throws Exception { Thread.sleep(500); - ts.cancel(); + to.cancel(); Thread.sleep(500); diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOnTest.java index 92d3528221..813fb2c25d 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOnTest.java @@ -103,7 +103,7 @@ public MaybeSource<Object> apply(Maybe<Object> v) throws Exception { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishProcessor<Integer> pp = PublishProcessor.create(); final Disposable[] ds = { null }; @@ -137,7 +137,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java index 535495e35e..d007667097 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeUsingTest.java @@ -26,7 +26,6 @@ import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class MaybeUsingTest { @@ -347,7 +346,6 @@ public void accept(Object d) throws Exception { .assertFailure(TestException.class); } - @Test public void emptyDisposerCrashes() { Maybe.using(new Callable<Object>() { @@ -411,14 +409,14 @@ public Object call() throws Exception { public MaybeSource<Integer> apply(Object v) throws Exception { return Maybe.wrap(new MaybeSource<Integer>() { @Override - public void subscribe(MaybeObserver<? super Integer> s) { + public void subscribe(MaybeObserver<? super Integer> observer) { Disposable d1 = Disposables.empty(); - s.onSubscribe(d1); + observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); - s.onSubscribe(d2); + observer.onSubscribe(d2); assertFalse(d1.isDisposed()); @@ -440,7 +438,7 @@ public void accept(Object d) throws Exception { @Test public void successDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -477,13 +475,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void errorDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -520,13 +518,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void emptyDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -562,7 +560,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java index be8bac0b87..8d07b6be3a 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipArrayTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.*; -import java.util.*; +import java.util.List; import org.junit.Test; @@ -26,7 +26,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class MaybeZipArrayTest { @@ -37,7 +36,6 @@ public Object apply(Object a, Object b) throws Exception { } }; - final Function3<Object, Object, Object, Object> addString3 = new Function3<Object, Object, Object, Object>() { @Override public Object apply(Object a, Object b, Object c) throws Exception { @@ -114,7 +112,7 @@ public void middleError() { @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -139,7 +137,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -151,6 +149,7 @@ public void run() { } } } + @SuppressWarnings("unchecked") @Test(expected = NullPointerException.class) public void zipArrayOneIsNull() { diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipIterableTest.java index bd43490bc5..87cf95d328 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeZipIterableTest.java @@ -27,7 +27,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class MaybeZipIterableTest { @@ -115,7 +114,7 @@ public void middleError() { @SuppressWarnings("unchecked") @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -141,7 +140,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); diff --git a/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java new file mode 100644 index 0000000000..8493f9a15e --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservableTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.*; + +public class CompletableAndThenObservableTest { + + @Test + public void cancelMain() { + CompletableSubject cs = CompletableSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = cs.andThen(ps) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + to.cancel(); + + assertFalse(cs.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void cancelOther() { + CompletableSubject cs = CompletableSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = cs.andThen(ps) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + cs.onComplete(); + + assertFalse(cs.hasObservers()); + assertTrue(ps.hasObservers()); + + to.cancel(); + + assertFalse(cs.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void errorMain() { + CompletableSubject cs = CompletableSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = cs.andThen(ps) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + cs.onError(new TestException()); + + assertFalse(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void errorOther() { + CompletableSubject cs = CompletableSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = cs.andThen(ps) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + cs.onComplete(); + + assertFalse(cs.hasObservers()); + assertTrue(ps.hasObservers()); + + ps.onError(new TestException()); + + assertFalse(cs.hasObservers()); + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void isDisposed() { + TestHelper.checkDisposed(Completable.never().andThen(Observable.never())); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisherTest.java b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisherTest.java new file mode 100644 index 0000000000..5f27eeeecf --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisherTest.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.functions.Function; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.CompletableSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class CompletableAndThenPublisherTest { + + @Test + public void cancelMain() { + CompletableSubject cs = CompletableSubject.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = cs.andThen(pp) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(pp.hasSubscribers()); + + ts.cancel(); + + assertFalse(cs.hasObservers()); + assertFalse(pp.hasSubscribers()); + } + + @Test + public void cancelOther() { + CompletableSubject cs = CompletableSubject.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = cs.andThen(pp) + .test(); + + assertTrue(cs.hasObservers()); + assertFalse(pp.hasSubscribers()); + + cs.onComplete(); + + assertFalse(cs.hasObservers()); + assertTrue(pp.hasSubscribers()); + + ts.cancel(); + + assertFalse(cs.hasObservers()); + assertFalse(pp.hasSubscribers()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeCompletableToFlowable(new Function<Completable, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Completable m) throws Exception { + return m.andThen(Flowable.never()); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletableTest.java new file mode 100644 index 0000000000..ef0e291581 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletableTest.java @@ -0,0 +1,391 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.CompletableSubject; + +public class FlowableConcatMapCompletableTest { + + @Test + public void simple() { + Flowable.range(1, 5) + .concatMapCompletable(Functions.justFunction(Completable.complete())) + .test() + .assertResult(); + } + + @Test + public void simple2() { + final AtomicInteger counter = new AtomicInteger(); + Flowable.range(1, 5) + .concatMapCompletable(Functions.justFunction(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }))) + .test() + .assertResult(); + + assertEquals(5, counter.get()); + } + + @Test + public void simpleLongPrefetch() { + Flowable.range(1, 1024) + .concatMapCompletable(Functions.justFunction(Completable.complete()), 32) + .test() + .assertResult(); + } + + @Test + public void mainError() { + Flowable.<Integer>error(new TestException()) + .concatMapCompletable(Functions.justFunction(Completable.complete())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Flowable.just(1) + .concatMapCompletable(Functions.justFunction(Completable.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerErrorDelayed() { + Flowable.range(1, 5) + .concatMapCompletableDelayError( + new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.error(new TestException()); + } + } + ) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestObserver<Void>>() { + @Override + public void accept(TestObserver<Void> to) throws Exception { + assertEquals(5, ((CompositeException)to.errors().get(0)).getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + Flowable.just(1) + .concatMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void immediateError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.concatMapCompletable( + Functions.justFunction(cs)).test(); + + to.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + + pp.onNext(1); + + assertTrue(cs.hasObservers()); + + pp.onError(new TestException()); + + assertFalse(cs.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void immediateError2() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.concatMapCompletable( + Functions.justFunction(cs)).test(); + + to.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + + pp.onNext(1); + + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + assertFalse(pp.hasSubscribers()); + + to.assertFailure(TestException.class); + } + + @Test + public void boundaryError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.concatMapCompletableDelayError( + Functions.justFunction(cs), false).test(); + + to.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + + pp.onNext(1); + + assertTrue(cs.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void endError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + final CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = pp.concatMapCompletableDelayError( + new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + if (v == 1) { + return cs; + } + return cs2; + } + }, true, 32 + ) + .test(); + + to.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + + pp.onNext(1); + + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + assertTrue(pp.hasSubscribers()); + + pp.onNext(2); + + to.assertEmpty(); + + cs2.onComplete(); + + assertTrue(pp.hasSubscribers()); + + to.assertEmpty(); + + pp.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowableToCompletable( + new Function<Flowable<Object>, Completable>() { + @Override + public Completable apply(Flowable<Object> f) + throws Exception { + return f.concatMapCompletable( + Functions.justFunction(Completable.complete())); + } + } + ); + } + + @Test + public void disposed() { + TestHelper.checkDisposed( + Flowable.never() + .concatMapCompletable( + Functions.justFunction(Completable.complete())) + ); + } + + @Test + public void queueOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + s.onNext(3); + s.onError(new TestException()); + } + } + .concatMapCompletable( + Functions.justFunction(Completable.never()), 1 + ) + .test() + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void immediateOuterInnerErrorRace() { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }) + .assertNotComplete(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void disposeInDrainLoop() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + final TestObserver<Void> to = pp.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + public void doneButNotEmpty() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + final TestObserver<Void> to = pp.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + pp.onNext(1); + pp.onNext(2); + pp.onComplete(); + + cs.onComplete(); + + to.assertResult(); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybeTest.java new file mode 100644 index 0000000000..5e6ef2c82b --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybeTest.java @@ -0,0 +1,428 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.mixed.FlowableConcatMapMaybe.ConcatMapMaybeSubscriber; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.internal.util.ErrorMode; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.MaybeSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatMapMaybeTest { + + @Test + public void simple() { + Flowable.range(1, 5) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleLong() { + Flowable.range(1, 1024) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }, 32) + .test() + .assertValueCount(1024) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void backpressure() { + TestSubscriber<Integer> ts = Flowable.range(1, 1024) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }, 32) + .test(0); + + for (int i = 1; i <= 1024; i++) { + ts.assertValueCount(i - 1) + .assertNoErrors() + .assertNotComplete() + .requestMore(1) + .assertValueCount(i) + .assertNoErrors(); + } + + ts.assertComplete(); + } + + @Test + public void empty() { + Flowable.range(1, 10) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }) + .test() + .assertResult(); + } + + @Test + public void mixed() { + Flowable.range(1, 10) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v); + } + return Maybe.empty(); + } + }) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void mixedLong() { + Flowable.range(1, 1024) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v).subscribeOn(Schedulers.computation()); + } + return Maybe.<Integer>empty().subscribeOn(Schedulers.computation()); + } + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertValueCount(512) + .assertNoErrors() + .assertComplete() + .assertOf(new Consumer<TestSubscriber<Integer>>() { + @Override + public void accept(TestSubscriber<Integer> ts) throws Exception { + for (int i = 0; i < 512; i ++) { + ts.assertValueAt(i, (i + 1) * 2); + } + } + }); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .concatMapMaybe(Functions.justFunction(Maybe.just(1))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Flowable.just(1) + .concatMapMaybe(Functions.justFunction(Maybe.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mainBoundaryErrorInnerSuccess() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.concatMapMaybeDelayError(Functions.justFunction(ms), false).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onSuccess(1); + + ts.assertFailure(TestException.class, 1); + } + + @Test + public void mainBoundaryErrorInnerEmpty() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.concatMapMaybeDelayError(Functions.justFunction(ms), false).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onComplete(); + + ts.assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable( + new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.concatMapMaybeDelayError( + Functions.justFunction(Maybe.empty())); + } + } + ); + } + + @Test + public void queueOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + s.onNext(3); + s.onError(new TestException()); + } + } + .concatMapMaybe( + Functions.justFunction(Maybe.never()), 1 + ) + .test() + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void limit() { + Flowable.range(1, 5) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .limit(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + Flowable.range(1, 5) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test(3) + .assertValues(1, 2, 3) + .assertNoErrors() + .assertNotComplete() + .cancel(); + } + + @Test + public void innerErrorAfterMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final AtomicReference<MaybeObserver<? super Integer>> obs = new AtomicReference<MaybeObserver<? super Integer>>(); + + TestSubscriber<Integer> ts = pp.concatMapMaybe( + new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return new Maybe<Integer>() { + @Override + protected void subscribeActual( + MaybeObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + obs.set(observer); + } + }; + } + } + ).test(); + + pp.onNext(1); + + pp.onError(new TestException("outer")); + obs.get().onError(new TestException("inner")); + + ts.assertFailureAndMessage(TestException.class, "outer"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayAllErrors() { + Flowable.range(1, 5) + .concatMapMaybeDelayError(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + return Maybe.error(new TestException()); + } + }) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestSubscriber<Object>>() { + @Override + public void accept(TestSubscriber<Object> ts) throws Exception { + CompositeException ce = (CompositeException)ts.errors().get(0); + assertEquals(5, ce.getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Object> ts = pp + .concatMapMaybe(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + + pp.onNext(1); + + ts.assertFailure(TestException.class); + + assertFalse(pp.hasSubscribers()); + } + + @Test(timeout = 10000) + public void cancelNoConcurrentClean() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ConcatMapMaybeSubscriber<Integer, Integer> operator = + new ConcatMapMaybeSubscriber<Integer, Integer>( + ts, Functions.justFunction(Maybe.<Integer>never()), 16, ErrorMode.IMMEDIATE); + + operator.onSubscribe(new BooleanSubscription()); + + operator.queue.offer(1); + + operator.getAndIncrement(); + + ts.cancel(); + + assertFalse(operator.queue.isEmpty()); + + operator.addAndGet(-2); + + operator.cancel(); + + assertTrue(operator.queue.isEmpty()); + } + + @Test + public void innerSuccessDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = Flowable.just(1) + .hide() + .concatMapMaybe(Functions.justFunction(ms)) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ms.onSuccess(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.dispose(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors(); + } + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingleTest.java new file mode 100644 index 0000000000..6f07102918 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingleTest.java @@ -0,0 +1,342 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.mixed.FlowableConcatMapSingle.ConcatMapSingleSubscriber; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.internal.util.ErrorMode; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.SingleSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableConcatMapSingleTest { + + @Test + public void simple() { + Flowable.range(1, 5) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleLong() { + Flowable.range(1, 1024) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }, 32) + .test() + .assertValueCount(1024) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void backpressure() { + TestSubscriber<Integer> ts = Flowable.range(1, 1024) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }, 32) + .test(0); + + for (int i = 1; i <= 1024; i++) { + ts.assertValueCount(i - 1) + .assertNoErrors() + .assertNotComplete() + .requestMore(1) + .assertValueCount(i) + .assertNoErrors(); + } + + ts.assertComplete(); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .concatMapSingle(Functions.justFunction(Single.just(1))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Flowable.just(1) + .concatMapSingle(Functions.justFunction(Single.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mainBoundaryErrorInnerSuccess() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + SingleSubject<Integer> ms = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.concatMapSingleDelayError(Functions.justFunction(ms), false).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onSuccess(1); + + ts.assertFailure(TestException.class, 1); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable( + new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) + throws Exception { + return f.concatMapSingleDelayError( + Functions.justFunction(Single.just((Object)1))); + } + } + ); + } + + @Test + public void queueOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + s.onNext(3); + s.onError(new TestException()); + } + } + .concatMapSingle( + Functions.justFunction(Single.never()), 1 + ) + .test() + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void limit() { + Flowable.range(1, 5) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .limit(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + Flowable.range(1, 5) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test(3) + .assertValues(1, 2, 3) + .assertNoErrors() + .assertNotComplete() + .cancel(); + } + + @Test + public void innerErrorAfterMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final AtomicReference<SingleObserver<? super Integer>> obs = new AtomicReference<SingleObserver<? super Integer>>(); + + TestSubscriber<Integer> ts = pp.concatMapSingle( + new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return new Single<Integer>() { + @Override + protected void subscribeActual( + SingleObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + obs.set(observer); + } + }; + } + } + ).test(); + + pp.onNext(1); + + pp.onError(new TestException("outer")); + obs.get().onError(new TestException("inner")); + + ts.assertFailureAndMessage(TestException.class, "outer"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayAllErrors() { + Flowable.range(1, 5) + .concatMapSingleDelayError(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + return Single.error(new TestException()); + } + }) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestSubscriber<Object>>() { + @Override + public void accept(TestSubscriber<Object> ts) throws Exception { + CompositeException ce = (CompositeException)ts.errors().get(0); + assertEquals(5, ce.getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Object> ts = pp + .concatMapSingle(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + + pp.onNext(1); + + ts.assertFailure(TestException.class); + + assertFalse(pp.hasSubscribers()); + } + + @Test(timeout = 10000) + public void cancelNoConcurrentClean() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ConcatMapSingleSubscriber<Integer, Integer> operator = + new ConcatMapSingleSubscriber<Integer, Integer>( + ts, Functions.justFunction(Single.<Integer>never()), 16, ErrorMode.IMMEDIATE); + + operator.onSubscribe(new BooleanSubscription()); + + operator.queue.offer(1); + + operator.getAndIncrement(); + + ts.cancel(); + + assertFalse(operator.queue.isEmpty()); + + operator.addAndGet(-2); + + operator.cancel(); + + assertTrue(operator.queue.isEmpty()); + } + + @Test + public void innerSuccessDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final SingleSubject<Integer> ss = SingleSubject.create(); + + final TestSubscriber<Integer> ts = Flowable.just(1) + .hide() + .concatMapSingle(Functions.justFunction(ss)) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ss.onSuccess(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.dispose(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletableTest.java new file mode 100644 index 0000000000..5bd6196fad --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletableTest.java @@ -0,0 +1,389 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.CompletableSubject; + +public class FlowableSwitchMapCompletableTest { + + @Test + public void normal() { + Flowable.range(1, 10) + .switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }) + .test() + .assertResult(); + } + + @Test + public void mainError() { + Flowable.<Integer>error(new TestException()) + .switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletable(Functions.justFunction(cs)) + .test(); + + assertTrue(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + + pp.onNext(1); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void switchOver() { + final CompletableSubject[] css = { + CompletableSubject.create(), + CompletableSubject.create() + }; + + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestObserver<Void> to = pp.switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return css[v]; + } + }) + .test(); + + to.assertEmpty(); + + pp.onNext(0); + + assertTrue(css[0].hasObservers()); + + pp.onNext(1); + + assertFalse(css[0].hasObservers()); + assertTrue(css[1].hasObservers()); + + pp.onComplete(); + + to.assertEmpty(); + + assertTrue(css[1].hasObservers()); + + css[1].onComplete(); + + to.assertResult(); + } + + @Test + public void dispose() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletable(Functions.justFunction(cs)) + .test(); + + pp.onNext(1); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + to.dispose(); + + assertFalse(pp.hasSubscribers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void checkDisposed() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestHelper.checkDisposed(pp.switchMapCompletable(Functions.justFunction(cs))); + } + + @Test + public void checkBadSource() { + TestHelper.checkDoubleOnSubscribeFlowableToCompletable(new Function<Flowable<Object>, Completable>() { + @Override + public Completable apply(Flowable<Object> f) throws Exception { + return f.switchMapCompletable(Functions.justFunction(Completable.never())); + } + }); + } + + @Test + public void mapperCrash() { + Flowable.range(1, 5).switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer f) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperCancels() { + final TestObserver<Void> to = new TestObserver<Void>(); + + Flowable.range(1, 5).switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer f) throws Exception { + to.cancel(); + return Completable.complete(); + } + }) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void onNextInnerCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletable(Functions.justFunction(cs)).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + public void onNextInnerErrorRace() { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletable(Functions.justFunction(cs)).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onErrorInnerErrorRace() { + final TestException ex0 = new TestException(); + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletable(Functions.justFunction(cs)).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex0); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void innerErrorThenMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onError(new TestException("main")); + } + } + .switchMapCompletable(Functions.justFunction(Completable.error(new TestException("inner")))) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "main"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorDelayed() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + pp.onNext(1); + + cs.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + + pp.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void mainCompletesinnerErrorDelayed() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + pp.onNext(1); + pp.onComplete(); + + to.assertEmpty(); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + } + + @Test + public void mainErrorDelayed() { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = pp.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + pp.onNext(1); + + pp.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(cs.hasObservers()); + + cs.onComplete(); + + to.assertFailure(TestException.class); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java new file mode 100644 index 0000000000..58188a34eb --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybeTest.java @@ -0,0 +1,648 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.MaybeSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableSwitchMapMaybeTest { + + @Test + public void simple() { + Flowable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleEmpty() { + Flowable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }) + .test() + .assertResult(); + } + + @Test + public void simpleMixed() { + Flowable.range(1, 10) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v); + } + return Maybe.empty(); + } + }) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void backpressured() { + TestSubscriber<Integer> ts = Flowable.range(1, 1024) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v); + } + return Maybe.empty(); + } + }) + .test(0L); + + // backpressure results items skipped + ts + .requestMore(1) + .assertResult(1024); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .switchMapMaybe(Functions.justFunction(Maybe.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Flowable.just(1) + .switchMapMaybe(Functions.justFunction(Maybe.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f + .switchMapMaybe(Functions.justFunction(Maybe.never())); + } + } + ); + } + + @Test + public void limit() { + Flowable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .limit(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void switchOver() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms1 = MaybeSubject.create(); + final MaybeSubject<Integer> ms2 = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + pp.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + assertFalse(pp.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + public void switchOverDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms1 = MaybeSubject.create(); + final MaybeSubject<Integer> ms2 = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + pp.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + + pp.onComplete(); + + ts.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerCompleteDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onComplete(); + + ts.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerSuccessDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onSuccess(1); + + ts.assertFailure(TestException.class, 1); + } + + @Test + public void mapperCrash() { + Flowable.just(1) + .switchMapMaybe(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void disposeBeforeSwitchInOnNext() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.just(1) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + ts.cancel(); + return Maybe.just(1); + } + }).subscribe(ts); + + ts.assertEmpty(); + } + + @Test + public void disposeOnNextAfterFirst() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.just(1, 2) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 2) { + ts.cancel(); + } + return Maybe.just(1); + } + }).subscribe(ts); + + ts.assertValue(1) + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void cancel() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + assertFalse(ms.hasObservers()); + } + + @Test + public void mainErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onError(new TestException("outer")); + } + } + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.error(new TestException("inner")); + } + }) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<MaybeObserver<? super Integer>> moRef = new AtomicReference<MaybeObserver<? super Integer>>(); + + TestSubscriber<Integer> ts = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onError(new TestException("outer")); + } + } + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return new Maybe<Integer>() { + @Override + protected void subscribeActual( + MaybeObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + moRef.set(observer); + } + }; + } + }) + .test(); + + ts.assertFailureAndMessage(TestException.class, "outer"); + + moRef.get().onError(new TestException("inner")); + moRef.get().onComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void nextInnerErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.never(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (ts.errorCount() != 0) { + assertTrue(errors.isEmpty()); + ts.assertFailure(TestException.class); + } else if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainErrorInnerErrorRace() { + final TestException ex = new TestException(); + final TestException ex2 = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.never(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + ts.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void nextInnerSuccessRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.empty(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onSuccess(3); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void requestMoreOnNext() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + requestMore(1); + } + }; + Flowable.range(1, 5) + .switchMapMaybe(Functions.justFunction(Maybe.just(1))) + .subscribe(ts); + + ts.assertResult(1, 1, 1, 1, 1); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java new file mode 100644 index 0000000000..803e9bf013 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingleTest.java @@ -0,0 +1,605 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.SingleSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableSwitchMapSingleTest { + + @Test + public void simple() { + Flowable.range(1, 5) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .switchMapSingle(Functions.justFunction(Single.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Flowable.just(1) + .switchMapSingle(Functions.justFunction(Single.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f + .switchMapSingle(Functions.justFunction(Single.never())); + } + } + ); + } + + @Test + public void limit() { + Flowable.range(1, 5) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .limit(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void switchOver() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms1 = SingleSubject.create(); + final SingleSubject<Integer> ms2 = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + pp.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + assertFalse(pp.hasSubscribers()); + + ts.assertFailure(TestException.class); + } + + @Test + public void switchOverDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms1 = SingleSubject.create(); + final SingleSubject<Integer> ms2 = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + pp.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + + pp.onComplete(); + + ts.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerCompleteDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onSuccess(1); + + ts.assertFailure(TestException.class, 1); + } + + @Test + public void mainErrorInnerSuccessDelayError() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + ts.assertEmpty(); + + ms.onSuccess(1); + + ts.assertFailure(TestException.class, 1); + } + + @Test + public void mapperCrash() { + Flowable.just(1) + .switchMapSingle(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void disposeBeforeSwitchInOnNext() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.just(1) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + ts.cancel(); + return Single.just(1); + } + }).subscribe(ts); + + ts.assertEmpty(); + } + + @Test + public void disposeOnNextAfterFirst() { + final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Flowable.just(1, 2) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 2) { + ts.cancel(); + } + return Single.just(1); + } + }).subscribe(ts); + + ts.assertValue(1) + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void cancel() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + ts.assertEmpty(); + + pp.onNext(1); + + ts.assertEmpty(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + ts.cancel(); + + assertFalse(pp.hasSubscribers()); + assertFalse(ms.hasObservers()); + } + + @Test + public void mainErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onError(new TestException("outer")); + } + } + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.error(new TestException("inner")); + } + }) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<SingleObserver<? super Integer>> moRef = new AtomicReference<SingleObserver<? super Integer>>(); + + TestSubscriber<Integer> ts = new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onError(new TestException("outer")); + } + } + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return new Single<Integer>() { + @Override + protected void subscribeActual( + SingleObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + moRef.set(observer); + } + }; + } + }) + .test(); + + ts.assertFailureAndMessage(TestException.class, "outer"); + + moRef.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void nextInnerErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (ts.errorCount() != 0) { + assertTrue(errors.isEmpty()); + ts.assertFailure(TestException.class); + } else if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainErrorInnerErrorRace() { + final TestException ex = new TestException(); + final TestException ex2 = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + ts.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void nextInnerSuccessRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestSubscriber<Integer> ts = pp.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + pp.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onSuccess(3); + } + }; + + TestHelper.race(r1, r2); + + ts.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void requestMoreOnNext() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + requestMore(1); + } + }; + Flowable.range(1, 5) + .switchMapSingle(Functions.justFunction(Single.just(1))) + .subscribe(ts); + + ts.assertResult(1, 1, 1, 1, 1); + } + + @Test + public void backpressured() { + Flowable.just(1) + .switchMapSingle(Functions.justFunction(Single.just(1))) + .test(0) + .assertEmpty() + .requestMore(1) + .assertResult(1); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservableTest.java new file mode 100644 index 0000000000..50ca4ffa01 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservableTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.*; + +public class MaybeFlatMapObservableTest { + + @Test + public void cancelMain() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ms.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ms.hasObservers()); + assertFalse(ps.hasObservers()); + + to.cancel(); + + assertFalse(ms.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void cancelOther() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ms.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ms.hasObservers()); + assertFalse(ps.hasObservers()); + + ms.onSuccess(1); + + assertFalse(ms.hasObservers()); + assertTrue(ps.hasObservers()); + + to.cancel(); + + assertFalse(ms.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void mapperCrash() { + Maybe.just(1).flatMapObservable(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void isDisposed() { + TestHelper.checkDisposed(Maybe.never().flatMapObservable(Functions.justFunction(Observable.never()))); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisherTest.java b/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisherTest.java new file mode 100644 index 0000000000..d784a9e1fc --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisherTest.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subjects.MaybeSubject; +import io.reactivex.subscribers.TestSubscriber; + +public class MaybeFlatMapPublisherTest { + + @Test + public void cancelMain() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = ms.flatMapPublisher(Functions.justFunction(pp)) + .test(); + + assertTrue(ms.hasObservers()); + assertFalse(pp.hasSubscribers()); + + ts.cancel(); + + assertFalse(ms.hasObservers()); + assertFalse(pp.hasSubscribers()); + } + + @Test + public void cancelOther() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); + + TestSubscriber<Integer> ts = ms.flatMapPublisher(Functions.justFunction(pp)) + .test(); + + assertTrue(ms.hasObservers()); + assertFalse(pp.hasSubscribers()); + + ms.onSuccess(1); + + assertFalse(ms.hasObservers()); + assertTrue(pp.hasSubscribers()); + + ts.cancel(); + + assertFalse(ms.hasObservers()); + assertFalse(pp.hasSubscribers()); + } + + @Test + public void mapperCrash() { + Maybe.just(1).flatMapPublisher(new Function<Integer, Publisher<? extends Object>>() { + @Override + public Publisher<? extends Object> apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeMaybeToFlowable(new Function<Maybe<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Maybe<Object> m) throws Exception { + return m.flatMapPublisher(Functions.justFunction(Flowable.never())); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletableTest.java new file mode 100644 index 0000000000..6f4ff458ab --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletableTest.java @@ -0,0 +1,434 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.ImmediateThinScheduler; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableConcatMapCompletableTest { + + @Test + public void simple() { + Observable.range(1, 5) + .concatMapCompletable(Functions.justFunction(Completable.complete())) + .test() + .assertResult(); + } + + @Test + public void simple2() { + final AtomicInteger counter = new AtomicInteger(); + Observable.range(1, 5) + .concatMapCompletable(Functions.justFunction(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + counter.incrementAndGet(); + } + }))) + .test() + .assertResult(); + + assertEquals(5, counter.get()); + } + + @Test + public void simpleLongPrefetch() { + Observable.range(1, 1024) + .concatMapCompletable(Functions.justFunction(Completable.complete()), 32) + .test() + .assertResult(); + } + + @Test + public void mainError() { + Observable.<Integer>error(new TestException()) + .concatMapCompletable(Functions.justFunction(Completable.complete())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.just(1) + .concatMapCompletable(Functions.justFunction(Completable.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerErrorDelayed() { + Observable.range(1, 5) + .concatMapCompletableDelayError( + new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.error(new TestException()); + } + } + ) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestObserver<Void>>() { + @Override + public void accept(TestObserver<Void> to) throws Exception { + assertEquals(5, ((CompositeException)to.errors().get(0)).getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + Observable.just(1) + .concatMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperCrashHidden() { + Observable.just(1).hide() + .concatMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void immediateError() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.concatMapCompletable( + Functions.justFunction(cs)).test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertFalse(cs.hasObservers()); + + ps.onNext(1); + + assertTrue(cs.hasObservers()); + + ps.onError(new TestException()); + + assertFalse(cs.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void immediateError2() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.concatMapCompletable( + Functions.justFunction(cs)).test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertFalse(cs.hasObservers()); + + ps.onNext(1); + + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void boundaryError() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.concatMapCompletableDelayError( + Functions.justFunction(cs), false).test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertFalse(cs.hasObservers()); + + ps.onNext(1); + + assertTrue(cs.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void endError() { + PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + final CompletableSubject cs2 = CompletableSubject.create(); + + TestObserver<Void> to = ps.concatMapCompletableDelayError( + new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + if (v == 1) { + return cs; + } + return cs2; + } + }, true, 32 + ) + .test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertFalse(cs.hasObservers()); + + ps.onNext(1); + + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + assertTrue(ps.hasObservers()); + + ps.onNext(2); + + to.assertEmpty(); + + cs2.onComplete(); + + assertTrue(ps.hasObservers()); + + to.assertEmpty(); + + ps.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservableToCompletable( + new Function<Observable<Object>, Completable>() { + @Override + public Completable apply(Observable<Object> f) + throws Exception { + return f.concatMapCompletable( + Functions.justFunction(Completable.complete())); + } + } + ); + } + + @Test + public void disposed() { + TestHelper.checkDisposed( + Observable.never() + .concatMapCompletable( + Functions.justFunction(Completable.complete())) + ); + } + + @Test + public void immediateOuterInnerErrorRace() { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }) + .assertNotComplete(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void disposeInDrainLoop() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + final TestObserver<Void> to = ps.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + public void doneButNotEmpty() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + final TestObserver<Void> to = ps.concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + ps.onNext(1); + ps.onNext(2); + ps.onComplete(); + + cs.onComplete(); + + to.assertResult(); + } + + @Test + public void asyncFused() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + final TestObserver<Void> to = ps.observeOn(ImmediateThinScheduler.INSTANCE) + .concatMapCompletable( + Functions.justFunction(cs) + ) + .test(); + + ps.onNext(1); + ps.onComplete(); + + cs.onComplete(); + + to.assertResult(); + } + + @Test + public void fusionRejected() { + final CompletableSubject cs = CompletableSubject.create(); + + TestHelper.rejectObservableFusion() + .concatMapCompletable( + Functions.justFunction(cs) + ) + .test() + .assertEmpty(); + } + + @Test + public void emptyScalarSource() { + final CompletableSubject cs = CompletableSubject.create(); + + Observable.empty() + .concatMapCompletable(Functions.justFunction(cs)) + .test() + .assertResult(); + } + + @Test + public void justScalarSource() { + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = Observable.just(1) + .concatMapCompletable(Functions.justFunction(cs)) + .test(); + + to.assertEmpty(); + + assertTrue(cs.hasObservers()); + + cs.onComplete(); + + to.assertResult(); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java new file mode 100644 index 0000000000..b02992e9fc --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java @@ -0,0 +1,449 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.mixed.ObservableConcatMapMaybe.ConcatMapMaybeMainObserver; +import io.reactivex.internal.util.ErrorMode; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.*; + +public class ObservableConcatMapMaybeTest { + + @Test + public void simple() { + Observable.range(1, 5) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleLong() { + Observable.range(1, 1024) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }, 32) + .test() + .assertValueCount(1024) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void empty() { + Observable.range(1, 10) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }) + .test() + .assertResult(); + } + + @Test + public void mixed() { + Observable.range(1, 10) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v); + } + return Maybe.empty(); + } + }) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void mixedLong() { + Observable.range(1, 1024) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v).subscribeOn(Schedulers.computation()); + } + return Maybe.<Integer>empty().subscribeOn(Schedulers.computation()); + } + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertValueCount(512) + .assertNoErrors() + .assertComplete() + .assertOf(new Consumer<TestObserver<Integer>>() { + @Override + public void accept(TestObserver<Integer> to) throws Exception { + for (int i = 0; i < 512; i ++) { + to.assertValueAt(i, (i + 1) * 2); + } + } + }); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .concatMapMaybe(Functions.justFunction(Maybe.just(1))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.just(1) + .concatMapMaybe(Functions.justFunction(Maybe.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mainBoundaryErrorInnerSuccess() { + PublishSubject<Integer> ps = PublishSubject.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.concatMapMaybeDelayError(Functions.justFunction(ms), false).test(); + + to.assertEmpty(); + + ps.onNext(1); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void mainBoundaryErrorInnerEmpty() { + PublishSubject<Integer> ps = PublishSubject.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.concatMapMaybeDelayError(Functions.justFunction(ms), false).test(); + + to.assertEmpty(); + + ps.onNext(1); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable( + new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f.concatMapMaybeDelayError( + Functions.justFunction(Maybe.empty())); + } + } + ); + } + + @Test + public void take() { + Observable.range(1, 5) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + Observable.range(1, 5).concatWith(Observable.<Integer>never()) + .concatMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test() + .assertValues(1, 2, 3, 4, 5) + .assertNoErrors() + .assertNotComplete() + .cancel(); + } + + @Test + public void mainErrorAfterInnerError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .concatMapMaybe( + Functions.justFunction(Maybe.error(new TestException("inner"))), 1 + ) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final AtomicReference<MaybeObserver<? super Integer>> obs = new AtomicReference<MaybeObserver<? super Integer>>(); + + TestObserver<Integer> to = ps.concatMapMaybe( + new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return new Maybe<Integer>() { + @Override + protected void subscribeActual( + MaybeObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + obs.set(observer); + } + }; + } + } + ).test(); + + ps.onNext(1); + + ps.onError(new TestException("outer")); + obs.get().onError(new TestException("inner")); + + to.assertFailureAndMessage(TestException.class, "outer"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayAllErrors() { + Observable.range(1, 5) + .concatMapMaybeDelayError(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + return Maybe.error(new TestException()); + } + }) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestObserver<Object>>() { + @Override + public void accept(TestObserver<Object> to) throws Exception { + CompositeException ce = (CompositeException)to.errors().get(0); + assertEquals(5, ce.getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Object> to = ps + .concatMapMaybe(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + to.assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void scalarMapperCrash() { + TestObserver<Object> to = Observable.just(1) + .concatMapMaybe(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Observable.just(1).hide() + .concatMapMaybe(Functions.justFunction(Maybe.never())) + ); + } + + @Test + public void scalarEmptySource() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + + Observable.empty() + .concatMapMaybe(Functions.justFunction(ms)) + .test() + .assertResult(); + + assertFalse(ms.hasObservers()); + } + + @Test(timeout = 10000) + public void cancelNoConcurrentClean() { + TestObserver<Integer> to = new TestObserver<Integer>(); + ConcatMapMaybeMainObserver<Integer, Integer> operator = + new ConcatMapMaybeMainObserver<Integer, Integer>( + to, Functions.justFunction(Maybe.<Integer>never()), 16, ErrorMode.IMMEDIATE); + + operator.onSubscribe(Disposables.empty()); + + operator.queue.offer(1); + + operator.getAndIncrement(); + + to.dispose(); + + assertFalse(operator.queue.isEmpty()); + + operator.addAndGet(-2); + + operator.dispose(); + + assertTrue(operator.queue.isEmpty()); + } + + @Test + public void checkUnboundedInnerQueue() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Observable + .fromArray(ms, Maybe.just(2), Maybe.just(3), Maybe.just(4)) + .concatMapMaybe(Functions.<Maybe<Integer>>identity(), 2) + .test(); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertResult(1, 2, 3, 4); + } + + @Test + public void innerSuccessDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestObserver<Integer> to = Observable.just(1) + .hide() + .concatMapMaybe(Functions.justFunction(ms)) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ms.onSuccess(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + to.dispose(); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java new file mode 100644 index 0000000000..cdd5326368 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java @@ -0,0 +1,387 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.mixed.ObservableConcatMapSingle.ConcatMapSingleMainObserver; +import io.reactivex.internal.util.ErrorMode; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableConcatMapSingleTest { + + @Test + public void simple() { + Observable.range(1, 5) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleLong() { + Observable.range(1, 1024) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }, 32) + .test() + .assertValueCount(1024) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .concatMapSingle(Functions.justFunction(Single.just(1))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.just(1) + .concatMapSingle(Functions.justFunction(Single.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mainBoundaryErrorInnerSuccess() { + PublishSubject<Integer> ps = PublishSubject.create(); + SingleSubject<Integer> ms = SingleSubject.create(); + + TestObserver<Integer> to = ps.concatMapSingleDelayError(Functions.justFunction(ms), false).test(); + + to.assertEmpty(); + + ps.onNext(1); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable( + new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f.concatMapSingleDelayError( + Functions.justFunction(Single.never())); + } + } + ); + } + + @Test + public void take() { + Observable.range(1, 5) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + Observable.range(1, 5).concatWith(Observable.<Integer>never()) + .concatMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test() + .assertValues(1, 2, 3, 4, 5) + .assertNoErrors() + .assertNotComplete() + .cancel(); + } + + @Test + public void mainErrorAfterInnerError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .concatMapSingle( + Functions.justFunction(Single.error(new TestException("inner"))), 1 + ) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final AtomicReference<SingleObserver<? super Integer>> obs = new AtomicReference<SingleObserver<? super Integer>>(); + + TestObserver<Integer> to = ps.concatMapSingle( + new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return new Single<Integer>() { + @Override + protected void subscribeActual( + SingleObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + obs.set(observer); + } + }; + } + } + ).test(); + + ps.onNext(1); + + ps.onError(new TestException("outer")); + obs.get().onError(new TestException("inner")); + + to.assertFailureAndMessage(TestException.class, "outer"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayAllErrors() { + Observable.range(1, 5) + .concatMapSingleDelayError(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + return Single.error(new TestException()); + } + }) + .test() + .assertFailure(CompositeException.class) + .assertOf(new Consumer<TestObserver<Object>>() { + @Override + public void accept(TestObserver<Object> to) throws Exception { + CompositeException ce = (CompositeException)to.errors().get(0); + assertEquals(5, ce.getExceptions().size()); + } + }); + } + + @Test + public void mapperCrash() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Object> to = ps + .concatMapSingle(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + to.assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void mapperCrashScalar() { + TestObserver<Object> to = Observable.just(1) + .concatMapSingle(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Observable.just(1).hide() + .concatMapSingle(Functions.justFunction(Single.never())) + ); + } + + @Test + public void mainCompletesWhileInnerActive() { + PublishSubject<Integer> ps = PublishSubject.create(); + SingleSubject<Integer> ms = SingleSubject.create(); + + TestObserver<Integer> to = ps.concatMapSingleDelayError(Functions.justFunction(ms), false).test(); + + to.assertEmpty(); + + ps.onNext(1); + ps.onNext(2); + ps.onComplete(); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertResult(1, 1); + } + + @Test + public void scalarEmptySource() { + SingleSubject<Integer> ss = SingleSubject.create(); + + Observable.empty() + .concatMapSingle(Functions.justFunction(ss)) + .test() + .assertResult(); + + assertFalse(ss.hasObservers()); + } + + @Test(timeout = 10000) + public void cancelNoConcurrentClean() { + TestObserver<Integer> to = new TestObserver<Integer>(); + ConcatMapSingleMainObserver<Integer, Integer> operator = + new ConcatMapSingleMainObserver<Integer, Integer>( + to, Functions.justFunction(Single.<Integer>never()), 16, ErrorMode.IMMEDIATE); + + operator.onSubscribe(Disposables.empty()); + + operator.queue.offer(1); + + operator.getAndIncrement(); + + to.cancel(); + + assertFalse(operator.queue.isEmpty()); + + operator.addAndGet(-2); + + operator.dispose(); + + assertTrue(operator.queue.isEmpty()); + } + + @Test + public void checkUnboundedInnerQueue() { + SingleSubject<Integer> ss = SingleSubject.create(); + + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Observable + .fromArray(ss, Single.just(2), Single.just(3), Single.just(4)) + .concatMapSingle(Functions.<Single<Integer>>identity(), 2) + .test(); + + to.assertEmpty(); + + ss.onSuccess(1); + + to.assertResult(1, 2, 3, 4); + } + + @Test + public void innerSuccessDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final SingleSubject<Integer> ss = SingleSubject.create(); + + final TestObserver<Integer> to = Observable.just(1) + .hide() + .concatMapSingle(Functions.justFunction(ss)) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ss.onSuccess(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + to.dispose(); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors(); + } + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java new file mode 100644 index 0000000000..c8b1bd3bc4 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java @@ -0,0 +1,431 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableSwitchMapCompletableTest { + + @Test + public void normal() { + Observable.range(1, 10) + .switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }) + .test() + .assertResult(); + } + + @Test + public void mainError() { + Observable.<Integer>error(new TestException()) + .switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(Functions.justFunction(cs)) + .test(); + + assertTrue(ps.hasObservers()); + assertFalse(cs.hasObservers()); + + ps.onNext(1); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void switchOver() { + final CompletableSubject[] css = { + CompletableSubject.create(), + CompletableSubject.create() + }; + + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return css[v]; + } + }) + .test(); + + to.assertEmpty(); + + ps.onNext(0); + + assertTrue(css[0].hasObservers()); + + ps.onNext(1); + + assertFalse(css[0].hasObservers()); + assertTrue(css[1].hasObservers()); + + ps.onComplete(); + + to.assertEmpty(); + + assertTrue(css[1].hasObservers()); + + css[1].onComplete(); + + to.assertResult(); + } + + @Test + public void dispose() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(Functions.justFunction(cs)) + .test(); + + ps.onNext(1); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + to.dispose(); + + assertFalse(ps.hasObservers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void checkDisposed() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestHelper.checkDisposed(ps.switchMapCompletable(Functions.justFunction(cs))); + } + + @Test + public void checkBadSource() { + TestHelper.checkDoubleOnSubscribeObservableToCompletable(new Function<Observable<Object>, Completable>() { + @Override + public Completable apply(Observable<Object> f) throws Exception { + return f.switchMapCompletable(Functions.justFunction(Completable.never())); + } + }); + } + + @Test + public void mapperCrash() { + Observable.range(1, 5).switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer f) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperCancels() { + final TestObserver<Void> to = new TestObserver<Void>(); + + Observable.range(1, 5).switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer f) throws Exception { + to.cancel(); + return Completable.complete(); + } + }) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void onNextInnerCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(Functions.justFunction(cs)).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertEmpty(); + } + } + + @Test + public void onNextInnerErrorRace() { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(Functions.justFunction(cs)).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onErrorInnerErrorRace() { + final TestException ex0 = new TestException(); + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletable(Functions.justFunction(cs)).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex0); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void innerErrorThenMainError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("main")); + } + } + .switchMapCompletable(Functions.justFunction(Completable.error(new TestException("inner")))) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "main"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorDelayed() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + ps.onNext(1); + + cs.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + + ps.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void mainCompletesinnerErrorDelayed() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + ps.onNext(1); + ps.onComplete(); + + to.assertEmpty(); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + } + + @Test + public void mainErrorDelayed() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = ps.switchMapCompletableDelayError(Functions.justFunction(cs)).test(); + + ps.onNext(1); + + ps.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(cs.hasObservers()); + + cs.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarMapperCrash() { + TestObserver<Void> to = Observable.just(1) + .switchMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + CompletableSubject cs = CompletableSubject.create(); + + Observable.empty() + .switchMapCompletable(Functions.justFunction(cs)) + .test() + .assertResult(); + + assertFalse(cs.hasObservers()); + } + + @Test + public void scalarSource() { + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Void> to = Observable.just(1) + .switchMapCompletable(Functions.justFunction(cs)) + .test(); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onComplete(); + + to.assertResult(); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java new file mode 100644 index 0000000000..7830b885da --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java @@ -0,0 +1,688 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableSwitchMapMaybeTest { + + @Test + public void simple() { + Observable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void simpleEmpty() { + Observable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.empty(); + } + }) + .test() + .assertResult(); + } + + @Test + public void simpleMixed() { + Observable.range(1, 10) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v % 2 == 0) { + return Maybe.just(v); + } + return Maybe.empty(); + } + }) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .switchMapMaybe(Functions.justFunction(Maybe.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.just(1) + .switchMapMaybe(Functions.justFunction(Maybe.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f + .switchMapMaybe(Functions.justFunction(Maybe.never())); + } + } + ); + } + + @Test + public void take() { + Observable.range(1, 5) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void switchOver() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms1 = MaybeSubject.create(); + final MaybeSubject<Integer> ms2 = MaybeSubject.create(); + + TestObserver<Integer> to = ps.switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + ps.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void switchOverDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms1 = MaybeSubject.create(); + final MaybeSubject<Integer> ms2 = MaybeSubject.create(); + + TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + ps.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + + ps.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerCompleteDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerSuccessDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void mapperCrash() { + Observable.just(1).hide() + .switchMapMaybe(new Function<Integer, MaybeSource<? extends Object>>() { + @Override + public MaybeSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void disposeBeforeSwitchInOnNext() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.just(1) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + to.cancel(); + return Maybe.just(1); + } + }).subscribe(to); + + to.assertEmpty(); + } + + @Test + public void disposeOnNextAfterFirst() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.just(1, 2) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 2) { + to.cancel(); + } + return Maybe.just(1); + } + }).subscribe(to); + + to.assertValue(1) + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void cancel() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + to.cancel(); + + assertFalse(ps.hasObservers()); + assertFalse(ms.hasObservers()); + } + + @Test + public void mainErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.error(new TestException("inner")); + } + }) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<MaybeObserver<? super Integer>> moRef = new AtomicReference<MaybeObserver<? super Integer>>(); + + TestObserver<Integer> to = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return new Maybe<Integer>() { + @Override + protected void subscribeActual( + MaybeObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + moRef.set(observer); + } + }; + } + }) + .test(); + + to.assertFailureAndMessage(TestException.class, "outer"); + + moRef.get().onError(new TestException("inner")); + moRef.get().onComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void nextInnerErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.never(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (to.errorCount() != 0) { + assertTrue(errors.isEmpty()); + to.assertFailure(TestException.class); + } else if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainErrorInnerErrorRace() { + final TestException ex = new TestException(); + final TestException ex2 = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.never(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void nextInnerSuccessRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final MaybeSubject<Integer> ms = MaybeSubject.create(); + + final TestObserver<Integer> to = ps.switchMapMaybeDelayError(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Maybe.empty(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onSuccess(3); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void checkDisposed() { + PublishSubject<Integer> ps = PublishSubject.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestHelper.checkDisposed(ps.switchMapMaybe(Functions.justFunction(ms))); + } + + @Test + public void drainReentrant() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + } + } + }; + + ps.switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + return Maybe.just(v); + } + }).subscribe(to); + + ps.onNext(1); + ps.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void scalarMapperCrash() { + TestObserver<Integer> to = Observable.just(1) + .switchMapMaybe(new Function<Integer, MaybeSource<Integer>>() { + @Override + public MaybeSource<Integer> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + + Observable.empty() + .switchMapMaybe(Functions.justFunction(ms)) + .test() + .assertResult(); + + assertFalse(ms.hasObservers()); + } + + @Test + public void scalarSource() { + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = Observable.just(1) + .switchMapMaybe(Functions.justFunction(ms)) + .test(); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(2); + + to.assertResult(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java new file mode 100644 index 0000000000..da35b10df1 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java @@ -0,0 +1,656 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableSwitchMapSingleTest { + + @Test + public void simple() { + Observable.range(1, 5) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .switchMapSingle(Functions.justFunction(Single.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.just(1) + .switchMapSingle(Functions.justFunction(Single.error(new TestException()))) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f + .switchMapSingle(Functions.justFunction(Single.never())); + } + } + ); + } + + @Test + public void take() { + Observable.range(1, 5) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void switchOver() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms1 = SingleSubject.create(); + final SingleSubject<Integer> ms2 = SingleSubject.create(); + + TestObserver<Integer> to = ps.switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + ps.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void switchOverDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms1 = SingleSubject.create(); + final SingleSubject<Integer> ms2 = SingleSubject.create(); + + TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms1; + } + return ms2; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms1.hasObservers()); + + ps.onNext(2); + + assertFalse(ms1.hasObservers()); + assertTrue(ms2.hasObservers()); + + ms2.onError(new TestException()); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + + ps.onComplete(); + + to.assertFailure(TestException.class); + } + + @Test + public void mainErrorInnerCompleteDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void mainErrorInnerSuccessDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(1); + + to.assertFailure(TestException.class, 1); + } + + @Test + public void mapperCrash() { + Observable.just(1).hide() + .switchMapSingle(new Function<Integer, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void disposeBeforeSwitchInOnNext() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.just(1).hide() + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + to.cancel(); + return Single.just(1); + } + }).subscribe(to); + + to.assertEmpty(); + } + + @Test + public void disposeOnNextAfterFirst() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.just(1, 2) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 2) { + to.cancel(); + } + return Single.just(1); + } + }).subscribe(to); + + to.assertValue(1) + .assertNoErrors() + .assertNotComplete(); + } + + @Test + public void cancel() { + PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + to.assertEmpty(); + + ps.onNext(1); + + to.assertEmpty(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + to.cancel(); + + assertFalse(ps.hasObservers()); + assertFalse(ms.hasObservers()); + } + + @Test + public void mainErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.error(new TestException("inner")); + } + }) + .test() + .assertFailureAndMessage(TestException.class, "inner"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "outer"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void innerErrorAfterTermination() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<SingleObserver<? super Integer>> moRef = new AtomicReference<SingleObserver<? super Integer>>(); + + TestObserver<Integer> to = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException("outer")); + } + } + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return new Single<Integer>() { + @Override + protected void subscribeActual( + SingleObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + moRef.set(observer); + } + }; + } + }) + .test(); + + to.assertFailureAndMessage(TestException.class, "outer"); + + moRef.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return ms; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void nextInnerErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (to.errorCount() != 0) { + assertTrue(errors.isEmpty()); + to.assertFailure(TestException.class); + } else if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainErrorInnerErrorRace() { + final TestException ex = new TestException(); + final TestException ex2 = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + to.assertError(new Predicate<Throwable>() { + @Override + public boolean test(Throwable e) throws Exception { + return e instanceof TestException || e instanceof CompositeException; + } + }); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void nextInnerSuccessRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final SingleSubject<Integer> ms = SingleSubject.create(); + + final TestObserver<Integer> to = ps.switchMapSingleDelayError(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + if (v == 1) { + return ms; + } + return Single.never(); + } + }).test(); + + ps.onNext(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ms.onSuccess(3); + } + }; + + TestHelper.race(r1, r2); + + to.assertNoErrors() + .assertNotComplete(); + } + } + + @Test + public void checkDisposed() { + PublishSubject<Integer> ps = PublishSubject.create(); + SingleSubject<Integer> ms = SingleSubject.create(); + + TestHelper.checkDisposed(ps.switchMapSingle(Functions.justFunction(ms))); + } + + @Test + public void drainReentrant() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + } + } + }; + + ps.switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + return Single.just(v); + } + }).subscribe(to); + + ps.onNext(1); + ps.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void scalarMapperCrash() { + TestObserver<Integer> to = Observable.just(1) + .switchMapSingle(new Function<Integer, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + SingleSubject<Integer> ss = SingleSubject.create(); + + Observable.empty() + .switchMapSingle(Functions.justFunction(ss)) + .test() + .assertResult(); + + assertFalse(ss.hasObservers()); + } + + @Test + public void scalarSource() { + SingleSubject<Integer> ss = SingleSubject.create(); + + TestObserver<Integer> to = Observable.just(1) + .switchMapSingle(Functions.justFunction(ss)) + .test(); + + assertTrue(ss.hasObservers()); + + to.assertEmpty(); + + ss.onSuccess(2); + + to.assertResult(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java new file mode 100644 index 0000000000..c30d0c4624 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import org.junit.Test; + +import io.reactivex.TestHelper; + +public class ScalarXMapZHelperTest { + + @Test + public void utilityClass() { + TestHelper.checkUtilityClass(ScalarXMapZHelper.class); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservableTest.java new file mode 100644 index 0000000000..a6b0d80344 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservableTest.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.mixed; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.*; + +public class SingleFlatMapObservableTest { + + @Test + public void cancelMain() { + SingleSubject<Integer> ss = SingleSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ss.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + to.cancel(); + + assertFalse(ss.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void cancelOther() { + SingleSubject<Integer> ss = SingleSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ss.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + ss.onSuccess(1); + + assertFalse(ss.hasObservers()); + assertTrue(ps.hasObservers()); + + to.cancel(); + + assertFalse(ss.hasObservers()); + assertFalse(ps.hasObservers()); + } + + @Test + public void errorMain() { + SingleSubject<Integer> ss = SingleSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ss.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + ss.onError(new TestException()); + + assertFalse(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void errorOther() { + SingleSubject<Integer> ss = SingleSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ss.flatMapObservable(Functions.justFunction(ps)) + .test(); + + assertTrue(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + ss.onSuccess(1); + + assertFalse(ss.hasObservers()); + assertTrue(ps.hasObservers()); + + ps.onError(new TestException()); + + assertFalse(ss.hasObservers()); + assertFalse(ps.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void mapperCrash() { + Single.just(1).flatMapObservable(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void isDisposed() { + TestHelper.checkDisposed(Single.never().flatMapObservable(Functions.justFunction(Observable.never()))); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java index cdd95c58f2..27aa27ec2d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java @@ -44,13 +44,13 @@ public void testSimple() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(timeout = 1000) @@ -69,13 +69,13 @@ public void testSameSourceMultipleIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } @@ -87,7 +87,7 @@ public void testEmpty() { Iterator<Long> it = iter.iterator(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); it.next(); } @@ -166,7 +166,7 @@ public void testFasterSource() { source.onNext(7); source.onComplete(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(expected = UnsupportedOperationException.class) diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java index 5a277d6ddd..a109beb2d7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java @@ -28,7 +28,7 @@ public class BlockingObservableMostRecentTest { @Test public void testMostRecentNull() { - assertEquals(null, Observable.<Void>never().blockingMostRecent(null).iterator().next()); + assertNull(Observable.<Void>never().blockingMostRecent(null).iterator().next()); } static <T> Iterable<T> mostRecent(Observable<T> source, T initialValue) { @@ -91,12 +91,12 @@ public void testSingleSourceManyIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java index 2b8e670c16..b854c317b2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java @@ -28,7 +28,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.internal.operators.observable.BlockingObservableNext.NextObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.BehaviorProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.*; @@ -323,7 +322,7 @@ public void testSingleSourceManyIterators() throws InterruptedException { BlockingObservableNext.NextIterator<Long> it = (BlockingObservableNext.NextIterator<Long>)iter.iterator(); for (long i = 0; i < 10; i++) { - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(j + "th iteration next", Long.valueOf(i), it.next()); } terminal.onNext(1); @@ -332,9 +331,9 @@ public void testSingleSourceManyIterators() throws InterruptedException { @Test public void testSynchronousNext() { - assertEquals(1, BehaviorProcessor.createDefault(1).take(1).blockingSingle().intValue()); - assertEquals(2, BehaviorProcessor.createDefault(2).blockingIterable().iterator().next().intValue()); - assertEquals(3, BehaviorProcessor.createDefault(3).blockingNext().iterator().next().intValue()); + assertEquals(1, BehaviorSubject.createDefault(1).take(1).blockingSingle().intValue()); + assertEquals(2, BehaviorSubject.createDefault(2).blockingIterable().iterator().next().intValue()); + assertEquals(3, BehaviorSubject.createDefault(3).blockingNext().iterator().next().intValue()); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java index f4fa707ee4..632d3dad72 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java @@ -121,6 +121,6 @@ public void testGetWithEmptyFlowable() throws Throwable { public void testGetWithASingleNullItem() throws Exception { Observable<String> obs = Observable.just((String)null); Future<String> f = obs.toFuture(); - assertEquals(null, f.get()); + assertNull(f.get()); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java index af22e83993..324b7c2172 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java @@ -16,15 +16,18 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.*; import io.reactivex.Observable; import io.reactivex.ObservableSource; import io.reactivex.Observer; -import io.reactivex.disposables.Disposables; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.internal.operators.observable.BlockingObservableIterable.BlockingObservableIterator; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; public class BlockingObservableToIteratorTest { @@ -34,16 +37,16 @@ public void testToIterator() { Iterator<String> it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("two", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("three", it.next()); - assertEquals(false, it.hasNext()); + assertFalse(it.hasNext()); } @@ -61,10 +64,10 @@ public void subscribe(Observer<? super String> observer) { Iterator<String> it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); it.next(); } @@ -119,4 +122,28 @@ public void remove() { BlockingObservableIterator<Integer> it = new BlockingObservableIterator<Integer>(128); it.remove(); } + + @Test(expected = NoSuchElementException.class) + public void disposedIteratorHasNextReturns() { + Iterator<Integer> it = PublishSubject.<Integer>create() + .blockingIterable().iterator(); + ((Disposable)it).dispose(); + assertFalse(it.hasNext()); + it.next(); + } + + @Test + public void asyncDisposeUnblocks() { + final Iterator<Integer> it = PublishSubject.<Integer>create() + .blockingIterable().iterator(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + ((Disposable)it).dispose(); + } + }, 1, TimeUnit.SECONDS); + + assertFalse(it.hasNext()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java index 29d2f8eff0..e53fd7161c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -124,6 +123,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamObservable() { Observable<Integer> source = Observable.just(1) @@ -143,10 +143,9 @@ public Observable<Integer> apply(Boolean t1) { assertEquals((Object)2, source.blockingFirst()); } - @Test public void testPredicateThrowsExceptionAndValueInCauseMessageObservable() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); @@ -156,17 +155,16 @@ public boolean test(String v) { throw ex; } }) - .subscribe(ts); + .subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME need to decide about adding the value that probably caused the crash in some way // assertTrue(ex.getCause().getMessage().contains("Boo!")); } - @Test public void testAll() { Observable<String> obs = Observable.just("one", "two", "six"); @@ -256,6 +254,7 @@ public boolean test(Integer i) { assertFalse(allOdd.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Observable<Integer> source = Observable.just(1) @@ -275,10 +274,9 @@ public Observable<Integer> apply(Boolean t1) { assertEquals((Object)2, source.blockingFirst()); } - @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); @@ -288,12 +286,12 @@ public boolean test(String v) { throw ex; } }) - .subscribe(ts); + .subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME need to decide about adding the value that probably caused the crash in some way // assertTrue(ex.getCause().getMessage().contains("Boo!")); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java index fd3d5aafc7..a76b8f22e6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java @@ -18,8 +18,8 @@ import java.io.IOException; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -29,7 +29,8 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Consumer; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; @@ -165,7 +166,7 @@ public void testSubscriptionOnlyHappensOnce() throws InterruptedException { final AtomicLong count = new AtomicLong(); Consumer<Disposable> incrementer = new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { count.incrementAndGet(); } }; @@ -176,10 +177,10 @@ public void accept(Disposable s) { //this stream emits second Observable<Integer> o2 = Observable.just(1).doOnSubscribe(incrementer) .delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.ambArray(o1, o2).subscribe(ts); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - ts.assertNoErrors(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.ambArray(o1, o2).subscribe(to); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); + to.assertNoErrors(); assertEquals(2, count.get()); } @@ -210,9 +211,9 @@ public void testAmbCancelsOthers() { PublishSubject<Integer> source2 = PublishSubject.create(); PublishSubject<Integer> source3 = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.ambArray(source1, source2, source3).subscribe(ts); + Observable.ambArray(source1, source2, source3).subscribe(to); assertTrue("Source 1 doesn't have subscribers!", source1.hasObservers()); assertTrue("Source 2 doesn't have subscribers!", source2.hasObservers()); @@ -263,6 +264,23 @@ public void singleIterable() { .assertResult(1); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void singleIterableNotSubclassingObservable() { + final ObservableSource<Integer> s1 = new ObservableSource<Integer>() { + @Override + public void subscribe (final Observer<? super Integer> observer) { + Observable.just(1).subscribe(observer); + } + }; + + Observable.amb(Collections.singletonList(s1)) + .test() + .assertResult(1); + } + @SuppressWarnings("unchecked") @Test public void disposed() { @@ -271,7 +289,7 @@ public void disposed() { @Test public void onNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps1 = PublishSubject.create(); final PublishSubject<Integer> ps2 = PublishSubject.create(); @@ -291,7 +309,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertSubscribed().assertNoErrors() .assertNotComplete().assertValueCount(1); @@ -300,7 +318,7 @@ public void run() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps1 = PublishSubject.create(); final PublishSubject<Integer> ps2 = PublishSubject.create(); @@ -320,7 +338,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertResult(); } @@ -328,7 +346,7 @@ public void run() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps1 = PublishSubject.create(); final PublishSubject<Integer> ps2 = PublishSubject.create(); @@ -352,7 +370,7 @@ public void run() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } finally { RxJavaPlugins.reset(); } @@ -383,4 +401,84 @@ public void ambArrayOrder() { Observable<Integer> error = Observable.error(new RuntimeException()); Observable.ambArray(Observable.just(1), error).test().assertValue(1).assertComplete(); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(Functions.emptyConsumer(), new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerCompleteDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Observable.ambArray( + Observable.empty() + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Observable.never() + ) + .subscribe(Functions.emptyConsumer(), Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java index 3ad9314430..fcdc13fee9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -231,6 +230,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingFirst()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstreamObservable() { Observable<Integer> source = Observable.just(1).isEmpty().toObservable() @@ -246,7 +246,7 @@ public Observable<Integer> apply(Boolean t1) { @Test public void testPredicateThrowsExceptionAndValueInCauseMessageObservable() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); Observable.just("Boo!").any(new Predicate<String>() { @@ -254,12 +254,12 @@ public void testPredicateThrowsExceptionAndValueInCauseMessageObservable() { public boolean test(String v) { throw ex; } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME value as last cause? // assertTrue(ex.getCause().getMessage().contains("Boo!")); } @@ -267,7 +267,7 @@ public boolean test(String v) { @Test public void testAnyWithTwoItems() { Observable<Integer> w = Observable.just(1, 2); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -276,7 +276,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -286,11 +286,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithTwoItems() { Observable<Integer> w = Observable.just(1, 2); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(true); verify(observer, times(1)).onSuccess(false); @@ -300,7 +300,7 @@ public void testIsEmptyWithTwoItems() { @Test public void testAnyWithOneItem() { Observable<Integer> w = Observable.just(1); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -309,7 +309,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -319,11 +319,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithOneItem() { Observable<Integer> w = Observable.just(1); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(true); verify(observer, times(1)).onSuccess(false); @@ -333,7 +333,7 @@ public void testIsEmptyWithOneItem() { @Test public void testAnyWithEmpty() { Observable<Integer> w = Observable.empty(); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer v) { return true; @@ -342,7 +342,7 @@ public boolean test(Integer v) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -352,11 +352,11 @@ public boolean test(Integer v) { @Test public void testIsEmptyWithEmpty() { Observable<Integer> w = Observable.empty(); - Single<Boolean> observable = w.isEmpty(); + Single<Boolean> single = w.isEmpty(); SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(true); verify(observer, never()).onSuccess(false); @@ -366,7 +366,7 @@ public void testIsEmptyWithEmpty() { @Test public void testAnyWithPredicate1() { Observable<Integer> w = Observable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; @@ -375,7 +375,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -385,7 +385,7 @@ public boolean test(Integer t1) { @Test public void testExists1() { Observable<Integer> w = Observable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 2; @@ -394,7 +394,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, never()).onSuccess(false); verify(observer, times(1)).onSuccess(true); @@ -404,7 +404,7 @@ public boolean test(Integer t1) { @Test public void testAnyWithPredicate2() { Observable<Integer> w = Observable.just(1, 2, 3); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t1) { return t1 < 1; @@ -413,7 +413,7 @@ public boolean test(Integer t1) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -424,7 +424,7 @@ public boolean test(Integer t1) { public void testAnyWithEmptyAndPredicate() { // If the source is empty, always output false. Observable<Integer> w = Observable.empty(); - Single<Boolean> observable = w.any(new Predicate<Integer>() { + Single<Boolean> single = w.any(new Predicate<Integer>() { @Override public boolean test(Integer t) { return true; @@ -433,7 +433,7 @@ public boolean test(Integer t) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(false); verify(observer, never()).onSuccess(true); @@ -452,6 +452,7 @@ public boolean test(Integer i) { assertTrue(anyEven.blockingGet()); } + @Test(timeout = 5000) public void testIssue1935NoUnsubscribeDownstream() { Observable<Integer> source = Observable.just(1).isEmpty() @@ -467,7 +468,7 @@ public Observable<Integer> apply(Boolean t1) { @Test public void testPredicateThrowsExceptionAndValueInCauseMessage() { - TestObserver<Boolean> ts = new TestObserver<Boolean>(); + TestObserver<Boolean> to = new TestObserver<Boolean>(); final IllegalArgumentException ex = new IllegalArgumentException(); Observable.just("Boo!").any(new Predicate<String>() { @@ -475,12 +476,12 @@ public void testPredicateThrowsExceptionAndValueInCauseMessage() { public boolean test(String v) { throw ex; } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(ex); + to.assertTerminated(); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(ex); // FIXME value as last cause? // assertTrue(ex.getCause().getMessage().contains("Boo!")); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java index d7b6046e42..16ba2fab6d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java @@ -14,11 +14,12 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.*; @@ -26,13 +27,18 @@ import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; -import io.reactivex.disposables.Disposables; -import io.reactivex.exceptions.TestException; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableBuffer.BufferExactObserver; +import io.reactivex.internal.operators.observable.ObservableBufferBoundarySupplier.BufferBoundarySupplierObserver; +import io.reactivex.internal.operators.observable.ObservableBufferTimed.*; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class ObservableBufferTest { @@ -341,7 +347,7 @@ public void testBufferStopsWhenUnsubscribed1() { Observable<Integer> source = Observable.never(); Observer<List<Integer>> o = TestHelper.mockObserver(); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(o); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(o); source.buffer(100, 200, TimeUnit.MILLISECONDS, scheduler) .doOnNext(new Consumer<List<Integer>>() { @@ -350,7 +356,7 @@ public void accept(List<Integer> pv) { System.out.println(pv); } }) - .subscribe(ts); + .subscribe(to); InOrder inOrder = Mockito.inOrder(o); @@ -358,7 +364,7 @@ public void accept(List<Integer> pv) { inOrder.verify(o, times(5)).onNext(Arrays.<Integer> asList()); - ts.dispose(); + to.dispose(); scheduler.advanceTimeBy(999, TimeUnit.MILLISECONDS); @@ -490,6 +496,7 @@ public void bufferWithBOBoundaryThrows() { verify(o, never()).onComplete(); verify(o, never()).onNext(any()); } + @Test(timeout = 2000) public void bufferWithSizeTake1() { Observable<Integer> source = Observable.just(1).repeat(); @@ -519,6 +526,7 @@ public void bufferWithSizeSkipTake1() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeTake1() { Observable<Long> source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -535,6 +543,7 @@ public void bufferWithTimeTake1() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithTimeSkipTake2() { Observable<Long> source = Observable.interval(40, 40, TimeUnit.MILLISECONDS, scheduler); @@ -553,6 +562,7 @@ public void bufferWithTimeSkipTake2() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test(timeout = 2000) public void bufferWithBoundaryTake2() { Observable<Long> boundary = Observable.interval(60, 60, TimeUnit.MILLISECONDS, scheduler); @@ -607,6 +617,7 @@ public void accept(List<Long> pv) { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void bufferWithSizeThrows() { PublishSubject<Integer> source = PublishSubject.create(); @@ -676,6 +687,7 @@ public void bufferWithTimeAndSize() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void bufferWithStartEndStartThrows() { PublishSubject<Integer> start = PublishSubject.create(); @@ -704,6 +716,7 @@ public Observable<Integer> apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndFunctionThrows() { PublishSubject<Integer> start = PublishSubject.create(); @@ -731,6 +744,7 @@ public Observable<Integer> apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void bufferWithStartEndEndThrows() { PublishSubject<Integer> start = PublishSubject.create(); @@ -764,16 +778,18 @@ public void testBufferWithTimeDoesntUnsubscribeDownstream() throws InterruptedEx final Observer<Object> o = TestHelper.mockObserver(); final CountDownLatch cdl = new CountDownLatch(1); - DisposableObserver<Object> s = new DisposableObserver<Object>() { + DisposableObserver<Object> observer = new DisposableObserver<Object>() { @Override public void onNext(Object t) { o.onNext(t); } + @Override public void onError(Throwable e) { o.onError(e); cdl.countDown(); } + @Override public void onComplete() { o.onComplete(); @@ -781,7 +797,7 @@ public void onComplete() { } }; - Observable.range(1, 1).delay(1, TimeUnit.SECONDS).buffer(2, TimeUnit.SECONDS).subscribe(s); + Observable.range(1, 1).delay(1, TimeUnit.SECONDS).buffer(2, TimeUnit.SECONDS).subscribe(observer); cdl.await(); @@ -789,7 +805,7 @@ public void onComplete() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); - assertFalse(s.isDisposed()); + assertFalse(observer.isDisposed()); } @SuppressWarnings("unchecked") @@ -1101,9 +1117,9 @@ public Collection<Object> call() throws Exception { @Test public void boundaryCancel() { - PublishSubject<Object> pp = PublishSubject.create(); + PublishSubject<Object> ps = PublishSubject.create(); - TestObserver<Collection<Object>> ts = pp + TestObserver<Collection<Object>> to = ps .buffer(Functions.justCallable(Observable.never()), new Callable<Collection<Object>>() { @Override public Collection<Object> call() throws Exception { @@ -1112,11 +1128,11 @@ public Collection<Object> call() throws Exception { }) .test(); - assertTrue(pp.hasObservers()); + assertTrue(ps.hasObservers()); - ts.dispose(); + to.dispose(); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); } @Test @@ -1170,9 +1186,9 @@ public Collection<Object> call() throws Exception { @SuppressWarnings("unchecked") @Test public void boundaryMainError() { - PublishSubject<Object> pp = PublishSubject.create(); + PublishSubject<Object> ps = PublishSubject.create(); - TestObserver<Collection<Object>> ts = pp + TestObserver<Collection<Object>> to = ps .buffer(Functions.justCallable(Observable.never()), new Callable<Collection<Object>>() { @Override public Collection<Object> call() throws Exception { @@ -1181,17 +1197,17 @@ public Collection<Object> call() throws Exception { }) .test(); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @SuppressWarnings("unchecked") @Test public void boundaryBoundaryError() { - PublishSubject<Object> pp = PublishSubject.create(); + PublishSubject<Object> ps = PublishSubject.create(); - TestObserver<Collection<Object>> ts = pp + TestObserver<Collection<Object>> to = ps .buffer(Functions.justCallable(Observable.error(new TestException())), new Callable<Collection<Object>>() { @Override public Collection<Object> call() throws Exception { @@ -1200,9 +1216,9 @@ public Collection<Object> call() throws Exception { }) .test(); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -1401,12 +1417,12 @@ public void bufferTimedExactBoundedError() { @Test public void withTimeAndSizeCapacityRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestScheduler scheduler = new TestScheduler(); final PublishSubject<Object> ps = PublishSubject.create(); - TestObserver<List<Object>> ts = ps.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); + TestObserver<List<Object>> to = ps.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); ps.onNext(1); ps.onNext(2); @@ -1432,11 +1448,707 @@ public void run() { ps.onComplete(); int items = 0; - for (List<Object> o : ts.values()) { + for (List<Object> o : to.values()) { items += o.size(); } assertEquals("Round: " + i, 5, items); } } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelExact() { + final AtomicInteger counter = new AtomicInteger(); + + Observable.<Integer>empty() + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(5, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelSkip() { + final AtomicInteger counter = new AtomicInteger(); + + Observable.<Integer>empty() + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(5, 10, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCompletionCancelOverlap() { + final AtomicInteger counter = new AtomicInteger(); + + Observable.<Integer>empty() + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }) + .buffer(10, 5, TimeUnit.SECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(Collections.<Integer>emptyList()); + + assertEquals(0, counter.get()); + } + + @Test + @SuppressWarnings("unchecked") + public void boundaryOpenCloseDisposedOnComplete() { + PublishSubject<Integer> source = PublishSubject.create(); + + PublishSubject<Integer> openIndicator = PublishSubject.create(); + + PublishSubject<Integer> closeIndicator = PublishSubject.create(); + + TestObserver<List<Integer>> to = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + assertTrue(source.hasObservers()); + assertTrue(openIndicator.hasObservers()); + assertFalse(closeIndicator.hasObservers()); + + openIndicator.onNext(1); + + assertTrue(openIndicator.hasObservers()); + assertTrue(closeIndicator.hasObservers()); + + source.onComplete(); + + to.assertResult(Collections.<Integer>emptyList()); + + assertFalse(openIndicator.hasObservers()); + assertFalse(closeIndicator.hasObservers()); + } + + @Test + public void bufferedCanCompleteIfOpenNeverCompletesDropping() { + Observable.range(1, 50) + .zipWith(Observable.interval(5, TimeUnit.MILLISECONDS), + new BiFunction<Integer, Long, Integer>() { + @Override + public Integer apply(Integer integer, Long aLong) { + return integer; + } + }) + .buffer(Observable.interval(0, 200, TimeUnit.MILLISECONDS), + new Function<Long, Observable<?>>() { + @Override + public Observable<?> apply(Long a) { + return Observable.just(a).delay(100, TimeUnit.MILLISECONDS); + } + }) + .test() + .assertSubscribed() + .awaitDone(3, TimeUnit.SECONDS) + .assertComplete(); + } + + @Test + public void bufferedCanCompleteIfOpenNeverCompletesOverlapping() { + Observable.range(1, 50) + .zipWith(Observable.interval(5, TimeUnit.MILLISECONDS), + new BiFunction<Integer, Long, Integer>() { + @Override + public Integer apply(Integer integer, Long aLong) { + return integer; + } + }) + .buffer(Observable.interval(0, 100, TimeUnit.MILLISECONDS), + new Function<Long, Observable<?>>() { + @Override + public Observable<?> apply(Long a) { + return Observable.just(a).delay(200, TimeUnit.MILLISECONDS); + } + }) + .test() + .assertSubscribed() + .awaitDone(3, TimeUnit.SECONDS) + .assertComplete(); + } + + @Test + @SuppressWarnings("unchecked") + public void openClosemainError() { + Observable.error(new TestException()) + .buffer(Observable.never(), Functions.justFunction(Observable.never())) + .test() + .assertFailure(TestException.class); + } + + @Test + @SuppressWarnings("unchecked") + public void openClosebadSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + Disposable bs1 = Disposables.empty(); + Disposable bs2 = Disposables.empty(); + + observer.onSubscribe(bs1); + + assertFalse(bs1.isDisposed()); + assertFalse(bs2.isDisposed()); + + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onError(new IOException()); + observer.onComplete(); + observer.onNext(1); + observer.onError(new TestException()); + } + } + .buffer(Observable.never(), Functions.justFunction(Observable.never())) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseOpenCompletes() { + PublishSubject<Integer> source = PublishSubject.create(); + + PublishSubject<Integer> openIndicator = PublishSubject.create(); + + PublishSubject<Integer> closeIndicator = PublishSubject.create(); + + TestObserver<List<Integer>> to = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + openIndicator.onNext(1); + + assertTrue(closeIndicator.hasObservers()); + + openIndicator.onComplete(); + + assertTrue(source.hasObservers()); + assertTrue(closeIndicator.hasObservers()); + + closeIndicator.onComplete(); + + assertFalse(source.hasObservers()); + + to.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseOpenCompletesNoBuffers() { + PublishSubject<Integer> source = PublishSubject.create(); + + PublishSubject<Integer> openIndicator = PublishSubject.create(); + + PublishSubject<Integer> closeIndicator = PublishSubject.create(); + + TestObserver<List<Integer>> to = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .test(); + + openIndicator.onNext(1); + + assertTrue(closeIndicator.hasObservers()); + + closeIndicator.onComplete(); + + assertTrue(source.hasObservers()); + assertTrue(openIndicator.hasObservers()); + + openIndicator.onComplete(); + + assertFalse(source.hasObservers()); + + to.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseTake() { + PublishSubject<Integer> source = PublishSubject.create(); + + PublishSubject<Integer> openIndicator = PublishSubject.create(); + + PublishSubject<Integer> closeIndicator = PublishSubject.create(); + + TestObserver<List<Integer>> to = source + .buffer(openIndicator, Functions.justFunction(closeIndicator)) + .take(1) + .test(); + + openIndicator.onNext(1); + closeIndicator.onComplete(); + + assertFalse(source.hasObservers()); + assertFalse(openIndicator.hasObservers()); + assertFalse(closeIndicator.hasObservers()); + + to.assertResult(Collections.<Integer>emptyList()); + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseBadOpen() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable.never() + .buffer(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + + assertFalse(((Disposable)observer).isDisposed()); + + Disposable bs1 = Disposables.empty(); + Disposable bs2 = Disposables.empty(); + + observer.onSubscribe(bs1); + + assertFalse(bs1.isDisposed()); + assertFalse(bs2.isDisposed()); + + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onError(new IOException()); + + assertTrue(((Disposable)observer).isDisposed()); + + observer.onComplete(); + observer.onNext(1); + observer.onError(new TestException()); + } + }, Functions.justFunction(Observable.never())) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void openCloseBadClose() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable.never() + .buffer(Observable.just(1).concatWith(Observable.<Integer>never()), + Functions.justFunction(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + + assertFalse(((Disposable)observer).isDisposed()); + + Disposable bs1 = Disposables.empty(); + Disposable bs2 = Disposables.empty(); + + observer.onSubscribe(bs1); + + assertFalse(bs1.isDisposed()); + assertFalse(bs2.isDisposed()); + + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onError(new IOException()); + + assertTrue(((Disposable)observer).isDisposed()); + + observer.onComplete(); + observer.onNext(1); + observer.onError(new TestException()); + } + })) + .test() + .assertFailure(IOException.class); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void bufferExactBoundaryDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable( + new Function<Observable<Object>, ObservableSource<List<Object>>>() { + @Override + public ObservableSource<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.buffer(Observable.never()); + } + } + ); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferExactBoundarySecondBufferCrash() { + PublishSubject<Integer> ps = PublishSubject.create(); + PublishSubject<Integer> b = PublishSubject.create(); + + TestObserver<List<Integer>> to = ps.buffer(b, new Callable<List<Integer>>() { + int calls; + @Override + public List<Integer> call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return new ArrayList<Integer>(); + } + }).test(); + + b.onNext(1); + + to.assertFailure(TestException.class); + } + + @SuppressWarnings("unchecked") + @Test + public void bufferExactBoundaryBadSource() { + Observable<Integer> ps = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onNext(1); + observer.onComplete(); + } + }; + + final AtomicReference<Observer<? super Integer>> ref = new AtomicReference<Observer<? super Integer>>(); + Observable<Integer> b = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }; + + TestObserver<List<Integer>> to = ps.buffer(b).test(); + + ref.get().onNext(1); + + to.assertResult(Collections.<Integer>emptyList()); + } + + @Test + public void bufferBoundaryErrorTwice() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorSubject.createDefault(1) + .buffer(Functions.justCallable(new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new TestException("first")); + observer.onError(new TestException("second")); + } + })) + .test() + .assertError(TestException.class) + .assertErrorMessage("first") + .assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "second"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void bufferBoundarySupplierDisposed() { + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + BufferBoundarySupplierObserver<Integer, List<Integer>, Integer> sub = + new BufferBoundarySupplierObserver<Integer, List<Integer>, Integer>( + to, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + Functions.justCallable(Observable.<Integer>never()) + ); + + Disposable bs = Disposables.empty(); + + sub.onSubscribe(bs); + + assertFalse(sub.isDisposed()); + + sub.dispose(); + + assertTrue(sub.isDisposed()); + + sub.next(); + + assertSame(DisposableHelper.DISPOSED, sub.other.get()); + + sub.dispose(); + sub.dispose(); + + assertTrue(bs.isDisposed()); + } + + @Test + public void bufferBoundarySupplierBufferAlreadyCleared() { + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + BufferBoundarySupplierObserver<Integer, List<Integer>, Integer> sub = + new BufferBoundarySupplierObserver<Integer, List<Integer>, Integer>( + to, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + Functions.justCallable(Observable.<Integer>never()) + ); + + Disposable bs = Disposables.empty(); + + sub.onSubscribe(bs); + + sub.buffer = null; + + sub.next(); + + sub.onNext(1); + + sub.onComplete(); + } + + @Test + public void timedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<List<Object>>>() { + @Override + public Observable<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.buffer(1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedCancelledUpfront() { + TestScheduler sch = new TestScheduler(); + + TestObserver<List<Object>> to = Observable.never() + .buffer(1, TimeUnit.MILLISECONDS, sch) + .test(true); + + sch.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + to.assertEmpty(); + } + + @Test + public void timedInternalState() { + TestScheduler sch = new TestScheduler(); + + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + + BufferExactUnboundedObserver<Integer, List<Integer>> sub = new BufferExactUnboundedObserver<Integer, List<Integer>>( + to, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), 1, TimeUnit.SECONDS, sch); + + sub.onSubscribe(Disposables.empty()); + + assertFalse(sub.isDisposed()); + + sub.onError(new TestException()); + sub.onNext(1); + sub.onComplete(); + + sub.run(); + + sub.dispose(); + + assertTrue(sub.isDisposed()); + + sub.buffer = new ArrayList<Integer>(); + sub.enter(); + sub.onComplete(); + } + + @Test + public void timedSkipDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<List<Object>>>() { + @Override + public Observable<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.buffer(2, 1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedSizedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<List<Object>>>() { + @Override + public Observable<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.buffer(2, TimeUnit.SECONDS, 10); + } + }); + } + + @Test + public void timedSkipInternalState() { + TestScheduler sch = new TestScheduler(); + + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + + BufferSkipBoundedObserver<Integer, List<Integer>> sub = new BufferSkipBoundedObserver<Integer, List<Integer>>( + to, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), 1, 1, TimeUnit.SECONDS, sch.createWorker()); + + sub.onSubscribe(Disposables.empty()); + + sub.enter(); + sub.onComplete(); + + sub.dispose(); + + sub.run(); + } + + @Test + public void timedSkipCancelWhenSecondBuffer() { + TestScheduler sch = new TestScheduler(); + + final TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + + BufferSkipBoundedObserver<Integer, List<Integer>> sub = new BufferSkipBoundedObserver<Integer, List<Integer>>( + to, new Callable<List<Integer>>() { + int calls; + @Override + public List<Integer> call() throws Exception { + if (++calls == 2) { + to.cancel(); + } + return new ArrayList<Integer>(); + } + }, 1, 1, TimeUnit.SECONDS, sch.createWorker()); + + sub.onSubscribe(Disposables.empty()); + + sub.run(); + + assertTrue(to.isCancelled()); + } + + @Test + public void timedSizeBufferAlreadyCleared() { + TestScheduler sch = new TestScheduler(); + + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + + BufferExactBoundedObserver<Integer, List<Integer>> sub = + new BufferExactBoundedObserver<Integer, List<Integer>>( + to, Functions.justCallable((List<Integer>)new ArrayList<Integer>()), + 1, TimeUnit.SECONDS, 1, false, sch.createWorker()) + ; + + Disposable bs = Disposables.empty(); + + sub.onSubscribe(bs); + + sub.producerIndex++; + + sub.run(); + + assertFalse(sub.isDisposed()); + + sub.enter(); + sub.onComplete(); + + sub.dispose(); + + assertTrue(sub.isDisposed()); + + sub.run(); + + sub.onNext(1); + } + + @Test + public void bufferExactDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<List<Object>>>() { + @Override + public ObservableSource<List<Object>> apply(Observable<Object> o) + throws Exception { + return o.buffer(1); + } + }); + } + + @Test + public void bufferExactState() { + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); + + BufferExactObserver<Integer, List<Integer>> sub = new BufferExactObserver<Integer, List<Integer>>( + to, 1, Functions.justCallable((List<Integer>)new ArrayList<Integer>()) + ); + + sub.onComplete(); + sub.onNext(1); + sub.onComplete(); + } + + @Test + public void bufferSkipDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<List<Object>>>() { + @Override + public ObservableSource<List<Object>> apply(Observable<Object> o) + throws Exception { + return o.buffer(1, 2); + } + }); + } + + @Test + @SuppressWarnings("unchecked") + public void bufferExactFailingSupplier() { + Observable.empty() + .buffer(1, TimeUnit.SECONDS, Schedulers.computation(), 10, new Callable<List<Object>>() { + @Override + public List<Object> call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .awaitDone(1, TimeUnit.SECONDS) + .assertFailure(TestException.class) + ; + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java index f985211c66..989206f156 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableCacheTest.java @@ -35,20 +35,20 @@ public class ObservableCacheTest { @Test public void testColdReplayNoBackpressure() { - ObservableCache<Integer> source = (ObservableCache<Integer>)ObservableCache.from(Observable.range(0, 1000)); + ObservableCache<Integer> source = new ObservableCache<Integer>(Observable.range(0, 1000), 16); assertFalse("Source is connected!", source.isConnected()); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.subscribe(ts); + source.subscribe(to); assertTrue("Source is not connected!", source.isConnected()); assertFalse("Subscribers retained!", source.hasObservers()); - ts.assertNoErrors(); - ts.assertTerminated(); - List<Integer> onNextEvents = ts.values(); + to.assertNoErrors(); + to.assertTerminated(); + List<Integer> onNextEvents = to.values(); assertEquals(1000, onNextEvents.size()); for (int i = 0; i < 1000; i++) { @@ -113,19 +113,19 @@ public void testUnsubscribeSource() throws Exception { o.subscribe(); o.subscribe(); o.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test public void testTake() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - ObservableCache<Integer> cached = (ObservableCache<Integer>)ObservableCache.from(Observable.range(1, 100)); - cached.take(10).subscribe(ts); + ObservableCache<Integer> cached = new ObservableCache<Integer>(Observable.range(1, 1000), 16); + cached.take(10).subscribe(to); - ts.assertNoErrors(); - ts.assertComplete(); - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertNoErrors(); + to.assertComplete(); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // ts.assertUnsubscribed(); // FIXME no longer valid assertFalse(cached.hasObservers()); } @@ -134,40 +134,41 @@ public void testTake() { public void testAsync() { Observable<Integer> source = Observable.range(1, 10000); for (int i = 0; i < 100; i++) { - TestObserver<Integer> ts1 = new TestObserver<Integer>(); + TestObserver<Integer> to1 = new TestObserver<Integer>(); - ObservableCache<Integer> cached = (ObservableCache<Integer>)ObservableCache.from(source); + ObservableCache<Integer> cached = new ObservableCache<Integer>(source, 16); - cached.observeOn(Schedulers.computation()).subscribe(ts1); + cached.observeOn(Schedulers.computation()).subscribe(to1); - ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts1.assertNoErrors(); - ts1.assertComplete(); - assertEquals(10000, ts1.values().size()); + to1.awaitTerminalEvent(2, TimeUnit.SECONDS); + to1.assertNoErrors(); + to1.assertComplete(); + assertEquals(10000, to1.values().size()); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); - cached.observeOn(Schedulers.computation()).subscribe(ts2); + TestObserver<Integer> to2 = new TestObserver<Integer>(); + cached.observeOn(Schedulers.computation()).subscribe(to2); - ts2.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts2.assertNoErrors(); - ts2.assertComplete(); - assertEquals(10000, ts2.values().size()); + to2.awaitTerminalEvent(2, TimeUnit.SECONDS); + to2.assertNoErrors(); + to2.assertComplete(); + assertEquals(10000, to2.values().size()); } } + @Test public void testAsyncComeAndGo() { Observable<Long> source = Observable.interval(1, 1, TimeUnit.MILLISECONDS) .take(1000) .subscribeOn(Schedulers.io()); - ObservableCache<Long> cached = (ObservableCache<Long>)ObservableCache.from(source); + ObservableCache<Long> cached = new ObservableCache<Long>(source, 16); Observable<Long> output = cached.observeOn(Schedulers.computation()); List<TestObserver<Long>> list = new ArrayList<TestObserver<Long>>(100); for (int i = 0; i < 100; i++) { - TestObserver<Long> ts = new TestObserver<Long>(); - list.add(ts); - output.skip(i * 10).take(10).subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + list.add(to); + output.skip(i * 10).take(10).subscribe(to); } List<Long> expected = new ArrayList<Long>(); @@ -175,16 +176,16 @@ public void testAsyncComeAndGo() { expected.add((long)(i - 10)); } int j = 0; - for (TestObserver<Long> ts : list) { - ts.awaitTerminalEvent(3, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertComplete(); + for (TestObserver<Long> to : list) { + to.awaitTerminalEvent(3, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertComplete(); for (int i = j * 10; i < j * 10 + 10; i++) { expected.set(i - j * 10, (long)i); } - ts.assertValueSequence(expected); + to.assertValueSequence(expected); j++; } @@ -204,14 +205,14 @@ public void subscribe(Observer<? super Integer> t) { } }); - TestObserver<Integer> ts = new TestObserver<Integer>(); - firehose.cache().observeOn(Schedulers.computation()).takeLast(100).subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + firehose.cache().observeOn(Schedulers.computation()).takeLast(100).subscribe(to); - ts.awaitTerminalEvent(3, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertComplete(); + to.awaitTerminalEvent(3, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertComplete(); - assertEquals(100, ts.values().size()); + assertEquals(100, to.values().size()); } @Test @@ -220,20 +221,19 @@ public void testValuesAndThenError() { .concatWith(Observable.<Integer>error(new TestException())) .cache(); + TestObserver<Integer> to = new TestObserver<Integer>(); + source.subscribe(to); - TestObserver<Integer> ts = new TestObserver<Integer>(); - source.subscribe(ts); - - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertNotComplete(); + to.assertError(TestException.class); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); - source.subscribe(ts2); + TestObserver<Integer> to2 = new TestObserver<Integer>(); + source.subscribe(to2); - ts2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - ts2.assertNotComplete(); - ts2.assertError(TestException.class); + to2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to2.assertNotComplete(); + to2.assertError(TestException.class); } @Test @@ -250,20 +250,20 @@ public void accept(Integer t) { }) .cache(); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { throw new TestException(); } }; - source.subscribe(ts); + source.subscribe(to); Assert.assertEquals(100, count.get()); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test @@ -318,7 +318,7 @@ public void take() { @Test public void subscribeEmitRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.<Integer>create(); final Observable<Integer> cache = ps.cache(); @@ -344,11 +344,30 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to .awaitDone(5, TimeUnit.SECONDS) .assertSubscribed().assertValueCount(500).assertComplete().assertNoErrors(); } } + + @Test + public void cancelledUpFront() { + final AtomicInteger call = new AtomicInteger(); + Observable<Object> f = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return call.incrementAndGet(); + } + }).concatWith(Observable.never()) + .cache(); + + f.test().assertValuesOnly(1); + + f.test(true) + .assertEmpty(); + + assertEquals(1, call.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java index 5a6c7263bb..736a0bd9b5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -483,7 +482,7 @@ public List<Object> apply(Object[] args) { final CountDownLatch cdl = new CountDownLatch(1); - Observer<List<Object>> s = new DefaultObserver<List<Object>>() { + Observer<List<Object>> observer = new DefaultObserver<List<Object>>() { @Override public void onNext(List<Object> t) { @@ -503,7 +502,7 @@ public void onComplete() { } }; - result.subscribe(s); + result.subscribe(observer); cdl.await(); @@ -756,14 +755,14 @@ public void accept(Notification<Long> n) { } }).take(SIZE); - TestObserver<Long> ts = new TestObserver<Long>(); + TestObserver<Long> to = new TestObserver<Long>(); Observable.combineLatest(timer, Observable.<Integer> never(), new BiFunction<Long, Integer, Long>() { @Override public Long apply(Long t1, Integer t2) { return t1; } - }).subscribe(ts); + }).subscribe(to); if (!latch.await(SIZE + 1000, TimeUnit.MILLISECONDS)) { fail("timed out"); @@ -788,6 +787,34 @@ public Object apply(Object[] a) throws Exception { .assertResult("[1, 2]"); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void combineLatestIterableOfSourcesNotSubclassingObservable() { + final ObservableSource<Integer> s1 = new ObservableSource<Integer>() { + @Override + public void subscribe (final Observer<? super Integer> observer) { + Observable.just(1).subscribe(observer); + } + }; + final ObservableSource<Integer> s2 = new ObservableSource<Integer>() { + @Override + public void subscribe (final Observer<? super Integer> observer) { + Observable.just(2).subscribe(observer); + } + }; + + Observable.combineLatest(Arrays.asList(s1, s2), new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return Arrays.toString(a); + } + }) + .test() + .assertResult("[1, 2]"); + } + @Test @SuppressWarnings("unchecked") public void combineLatestDelayErrorArrayOfSources() { @@ -963,7 +990,7 @@ public Object apply(Object[] a) throws Exception { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishSubject<Integer> ps1 = PublishSubject.create(); @@ -1170,7 +1197,7 @@ public void eagerDispose() { final PublishSubject<Integer> ps1 = PublishSubject.create(); final PublishSubject<Integer> ps2 = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -1192,10 +1219,30 @@ public Integer apply(Integer t1, Integer t2) throws Exception { return t1 + t2; } }) - .subscribe(ts); + .subscribe(to); ps1.onNext(1); ps2.onNext(2); - ts.assertResult(3); + to.assertResult(3); } + + @Test + @SuppressWarnings("unchecked") + public void syncFirstErrorsAfterItemDelayError() { + Observable.combineLatestDelayError(Arrays.asList( + Observable.just(21).concatWith(Observable.<Integer>error(new TestException())), + Observable.just(21).delay(100, TimeUnit.MILLISECONDS) + ), + new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return (Integer)a[0] + (Integer)a[1]; + } + } + ) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class, 42); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java new file mode 100644 index 0000000000..ab34d4e03e --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapCompletableTest.java @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableConcatMapCompletableTest { + + @Test + public void asyncFused() throws Exception { + UnicastSubject<Integer> us = UnicastSubject.create(); + + TestObserver<Void> to = us.concatMapCompletable(completableComplete(), 2).test(); + + us.onNext(1); + us.onComplete(); + + to.assertComplete(); + to.assertValueCount(0); + } + + @Test + public void notFused() throws Exception { + UnicastSubject<Integer> us = UnicastSubject.create(); + TestObserver<Void> to = us.hide().concatMapCompletable(completableComplete(), 2).test(); + + us.onNext(1); + us.onNext(2); + us.onComplete(); + + to.assertComplete(); + to.assertValueCount(0); + to.assertNoErrors(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Observable.<Integer>just(1).hide() + .concatMapCompletable(completableError())); + } + + @Test + public void mainError() { + Observable.<Integer>error(new TestException()) + .concatMapCompletable(completableComplete()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void innerError() { + Observable.<Integer>just(1).hide() + .concatMapCompletable(completableError()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void badSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .concatMapCompletable(completableComplete()) + .test() + .assertComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onErrorRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps1 = PublishSubject.create(); + final PublishSubject<Integer> ps2 = PublishSubject.create(); + + TestObserver<Void> to = ps1.concatMapCompletable(new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.fromObservable(ps2); + } + }).test(); + + final TestException ex1 = new TestException(); + final TestException ex2 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps1.onError(ex1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ps2.onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + to.assertFailure(TestException.class); + + if (!errors.isEmpty()) { + TestHelper.assertError(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mapperThrows() { + Observable.just(1).hide() + .concatMapCompletable(completableThrows()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void fusedPollThrows() { + Observable.just(1) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .concatMapCompletable(completableComplete()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void concatReportsDisposedOnComplete() { + final Disposable[] disposable = { null }; + + Observable.just(1) + .hide() + .concatMapCompletable(completableComplete()) + .subscribe(new CompletableObserver() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + @Test + public void concatReportsDisposedOnError() { + final Disposable[] disposable = { null }; + + Observable.just(1) + .hide() + .concatMapCompletable(completableError()) + .subscribe(new CompletableObserver() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + private Function<Integer, CompletableSource> completableComplete() { + return new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.complete(); + } + }; + } + + private Function<Integer, CompletableSource> completableError() { + return new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + return Completable.error(new TestException()); + } + }; + } + + private Function<Integer, CompletableSource> completableThrows() { + return new Function<Integer, CompletableSource>() { + @Override + public CompletableSource apply(Integer v) throws Exception { + throw new TestException(); + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java index 1e1b814865..17e418bab7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapEagerTest.java @@ -50,7 +50,7 @@ public ObservableSource<Integer> apply(Integer t) { @Test @Ignore("Observable doesn't do backpressure") public void normalBackpressured() { -// TestObserver<Integer> ts = Observable.range(1, 5) +// TestObserver<Integer> to = Observable.range(1, 5) // .concatMapEager(new Function<Integer, ObservableSource<Integer>>() { // @Override // public ObservableSource<Integer> apply(Integer t) { @@ -59,19 +59,19 @@ public void normalBackpressured() { // }) // .test(3); // -// ts.assertValues(1, 2, 2); +// to.assertValues(1, 2, 2); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3); +// to.assertValues(1, 2, 2, 3); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3, 3); +// to.assertValues(1, 2, 2, 3, 3); // -// ts.request(5); +// to.request(5); // -// ts.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); +// to.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); } @Test @@ -90,7 +90,7 @@ public ObservableSource<Integer> apply(Integer t) { @Test @Ignore("Observable doesn't do backpressure") public void normalDelayBoundaryBackpressured() { -// TestObserver<Integer> ts = Observable.range(1, 5) +// TestObserver<Integer> to = Observable.range(1, 5) // .concatMapEagerDelayError(new Function<Integer, ObservableSource<Integer>>() { // @Override // public ObservableSource<Integer> apply(Integer t) { @@ -99,19 +99,19 @@ public void normalDelayBoundaryBackpressured() { // }, false) // .test(3); // -// ts.assertValues(1, 2, 2); +// to.assertValues(1, 2, 2); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3); +// to.assertValues(1, 2, 2, 3); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3, 3); +// to.assertValues(1, 2, 2, 3, 3); // -// ts.request(5); +// to.request(5); // -// ts.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); +// to.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); } @Test @@ -130,7 +130,7 @@ public ObservableSource<Integer> apply(Integer t) { @Test @Ignore("Observable doesn't do backpressure") public void normalDelayEndBackpressured() { -// TestObserver<Integer> ts = Observable.range(1, 5) +// TestObserver<Integer> to = Observable.range(1, 5) // .concatMapEagerDelayError(new Function<Integer, ObservableSource<Integer>>() { // @Override // public ObservableSource<Integer> apply(Integer t) { @@ -139,19 +139,19 @@ public void normalDelayEndBackpressured() { // }, true) // .test(3); // -// ts.assertValues(1, 2, 2); +// to.assertValues(1, 2, 2); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3); +// to.assertValues(1, 2, 2, 3); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 2, 3, 3); +// to.assertValues(1, 2, 2, 3, 3); // -// ts.request(5); +// to.request(5); // -// ts.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); +// to.assertResult(1, 2, 2, 3, 3, 4, 4, 5, 5, 6); } @Test @@ -159,7 +159,7 @@ public void mainErrorsDelayBoundary() { PublishSubject<Integer> main = PublishSubject.create(); final PublishSubject<Integer> inner = PublishSubject.create(); - TestObserver<Integer> ts = main.concatMapEagerDelayError( + TestObserver<Integer> to = main.concatMapEagerDelayError( new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer t) { @@ -171,16 +171,16 @@ public ObservableSource<Integer> apply(Integer t) { inner.onNext(2); - ts.assertValue(2); + to.assertValue(2); main.onError(new TestException("Forced failure")); - ts.assertNoErrors(); + to.assertNoErrors(); inner.onNext(3); inner.onComplete(); - ts.assertFailureAndMessage(TestException.class, "Forced failure", 2, 3); + to.assertFailureAndMessage(TestException.class, "Forced failure", 2, 3); } @Test @@ -188,7 +188,7 @@ public void mainErrorsDelayEnd() { PublishSubject<Integer> main = PublishSubject.create(); final PublishSubject<Integer> inner = PublishSubject.create(); - TestObserver<Integer> ts = main.concatMapEagerDelayError( + TestObserver<Integer> to = main.concatMapEagerDelayError( new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer t) { @@ -201,16 +201,16 @@ public ObservableSource<Integer> apply(Integer t) { inner.onNext(2); - ts.assertValue(2); + to.assertValue(2); main.onError(new TestException("Forced failure")); - ts.assertNoErrors(); + to.assertNoErrors(); inner.onNext(3); inner.onComplete(); - ts.assertFailureAndMessage(TestException.class, "Forced failure", 2, 3, 2, 3); + to.assertFailureAndMessage(TestException.class, "Forced failure", 2, 3, 2, 3); } @Test @@ -218,7 +218,7 @@ public void mainErrorsImmediate() { PublishSubject<Integer> main = PublishSubject.create(); final PublishSubject<Integer> inner = PublishSubject.create(); - TestObserver<Integer> ts = main.concatMapEager( + TestObserver<Integer> to = main.concatMapEager( new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer t) { @@ -231,7 +231,7 @@ public ObservableSource<Integer> apply(Integer t) { inner.onNext(2); - ts.assertValue(2); + to.assertValue(2); main.onError(new TestException("Forced failure")); @@ -240,7 +240,7 @@ public ObservableSource<Integer> apply(Integer t) { inner.onNext(3); inner.onComplete(); - ts.assertFailureAndMessage(TestException.class, "Forced failure", 2); + to.assertFailureAndMessage(TestException.class, "Forced failure", 2); } @Test @@ -259,7 +259,7 @@ public ObservableSource<Integer> apply(Integer v) { .assertComplete(); } - TestObserver<Object> ts; + TestObserver<Object> to; Function<Integer, Observable<Integer>> toJust = new Function<Integer, Observable<Integer>>() { @Override @@ -277,25 +277,25 @@ public Observable<Integer> apply(Integer t) { @Before public void before() { - ts = new TestObserver<Object>(); + to = new TestObserver<Object>(); } @Test public void testSimple() { - Observable.range(1, 100).concatMapEager(toJust).subscribe(ts); + Observable.range(1, 100).concatMapEager(toJust).subscribe(to); - ts.assertNoErrors(); - ts.assertValueCount(100); - ts.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(100); + to.assertComplete(); } @Test public void testSimple2() { - Observable.range(1, 100).concatMapEager(toRange).subscribe(ts); + Observable.range(1, 100).concatMapEager(toRange).subscribe(to); - ts.assertNoErrors(); - ts.assertValueCount(200); - ts.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(200); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -309,13 +309,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source).subscribe(ts); + Observable.concatArrayEager(source, source).subscribe(to); Assert.assertEquals(2, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -329,13 +329,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source).subscribe(to); Assert.assertEquals(3, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -349,13 +349,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source).subscribe(to); Assert.assertEquals(4, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -369,13 +369,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source, source).subscribe(to); Assert.assertEquals(5, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -389,13 +389,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source, source, source).subscribe(to); Assert.assertEquals(6, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -409,13 +409,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source, source, source, source).subscribe(to); Assert.assertEquals(7, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -429,13 +429,13 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source, source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source, source, source, source, source).subscribe(to); Assert.assertEquals(8, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -449,22 +449,22 @@ public void accept(Integer t) { } }).hide(); - Observable.concatArrayEager(source, source, source, source, source, source, source, source, source).subscribe(ts); + Observable.concatArrayEager(source, source, source, source, source, source, source, source, source).subscribe(to); Assert.assertEquals(9, count.get()); - ts.assertValueCount(count.get()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(count.get()); + to.assertNoErrors(); + to.assertComplete(); } @Test public void testMainError() { - Observable.<Integer>error(new TestException()).concatMapEager(toJust).subscribe(ts); + Observable.<Integer>error(new TestException()).concatMapEager(toJust).subscribe(to); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @SuppressWarnings("unchecked") @@ -475,23 +475,23 @@ public void testInnerError() { PublishSubject<Integer> ps = PublishSubject.create(); Observable.concatArrayEager(Observable.just(1), ps) - .subscribe(ts); + .subscribe(to); ps.onError(new TestException()); - ts.assertValue(1); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertValue(1); + to.assertError(TestException.class); + to.assertNotComplete(); } @SuppressWarnings("unchecked") @Test public void testInnerEmpty() { - Observable.concatArrayEager(Observable.empty(), Observable.empty()).subscribe(ts); + Observable.concatArrayEager(Observable.empty(), Observable.empty()).subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertComplete(); } @Test @@ -501,11 +501,11 @@ public void testMapperThrows() { public Observable<Integer> apply(Integer t) { throw new TestException(); } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test(expected = IllegalArgumentException.class) @@ -546,11 +546,11 @@ public void testAsynchronousRun() { public Observable<Integer> apply(Integer t) { return Observable.range(1, 1000).subscribeOn(Schedulers.computation()); } - }).observeOn(Schedulers.newThread()).subscribe(ts); + }).observeOn(Schedulers.newThread()).subscribe(to); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertValueCount(2000); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertValueCount(2000); } @Test @@ -573,13 +573,13 @@ public void accept(Integer t) { } } }) - .subscribe(ts); + .subscribe(to); subject.onNext(1); - ts.assertNoErrors(); - ts.assertNotComplete(); - ts.assertValues(1, 2); + to.assertNoErrors(); + to.assertNotComplete(); + to.assertValues(1, 2); } @Test @@ -587,7 +587,7 @@ public void accept(Integer t) { public void testPrefetchIsBounded() { final AtomicInteger count = new AtomicInteger(); - TestObserver<Object> ts = TestObserver.create(); + TestObserver<Object> to = TestObserver.create(); Observable.just(1).concatMapEager(new Function<Integer, Observable<Integer>>() { @Override @@ -600,11 +600,11 @@ public void accept(Integer t) { } }).hide(); } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoErrors(); - ts.assertNoValues(); - ts.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); + to.assertNotComplete(); Assert.assertEquals(Observable.bufferSize(), count.get()); } @@ -616,14 +616,13 @@ public void testInnerNull() { public Observable<Integer> apply(Integer t) { return Observable.just(null); } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoErrors(); - ts.assertComplete(); - ts.assertValue(null); + to.assertNoErrors(); + to.assertComplete(); + to.assertValue(null); } - @Test @Ignore("Observable doesn't do backpressure") public void testMaxConcurrent5() { @@ -663,13 +662,13 @@ public void many() throws Exception { Method m = Observable.class.getMethod("concatEager", clazz); - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ((Observable<Integer>)m.invoke(null, (Object[])obs)).subscribe(ts); + ((Observable<Integer>)m.invoke(null, (Object[])obs)).subscribe(to); - ts.assertValues(expected); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues(expected); + to.assertNoErrors(); + to.assertComplete(); } } @@ -677,37 +676,37 @@ public void many() throws Exception { @Test public void capacityHint() { Observable<Integer> source = Observable.just(1); - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - Observable.concatEager(Arrays.asList(source, source, source), 1, 1).subscribe(ts); + Observable.concatEager(Arrays.asList(source, source, source), 1, 1).subscribe(to); - ts.assertValues(1, 1, 1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues(1, 1, 1); + to.assertNoErrors(); + to.assertComplete(); } @Test public void Observable() { Observable<Integer> source = Observable.just(1); - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - Observable.concatEager(Observable.just(source, source, source)).subscribe(ts); + Observable.concatEager(Observable.just(source, source, source)).subscribe(to); - ts.assertValues(1, 1, 1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues(1, 1, 1); + to.assertNoErrors(); + to.assertComplete(); } @Test public void ObservableCapacityHint() { Observable<Integer> source = Observable.just(1); - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - Observable.concatEager(Observable.just(source, source, source), 1, 1).subscribe(ts); + Observable.concatEager(Observable.just(source, source, source), 1, 1).subscribe(to); - ts.assertValues(1, 1, 1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues(1, 1, 1); + to.assertNoErrors(); + to.assertComplete(); } @SuppressWarnings("unchecked") @@ -807,7 +806,7 @@ public Integer call() throws Exception { @Test public void innerOuterRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishSubject<Integer> ps1 = PublishSubject.create(); @@ -838,7 +837,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertSubscribed().assertNoValues().assertNotComplete(); @@ -862,7 +861,7 @@ public void run() { @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps1 = PublishSubject.create(); final TestObserver<Integer> to = ps1.concatMapEager(new Function<Integer, ObservableSource<Integer>>() { @@ -885,7 +884,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertEmpty(); } @@ -1001,4 +1000,178 @@ public ObservableSource<Integer> apply(Integer i) throws Exception { .assertResult(1, 2, 3, 4, 5) ; } + + @Test + @SuppressWarnings("unchecked") + public void maxConcurrencyOf2() { + List<Integer>[] list = new ArrayList[100]; + for (int i = 0; i < 100; i++) { + List<Integer> lst = new ArrayList<Integer>(); + list[i] = lst; + for (int k = 1; k <= 10; k++) { + lst.add((i) * 10 + k); + } + } + + Observable.range(1, 1000) + .buffer(10) + .concatMapEager(new Function<List<Integer>, ObservableSource<List<Integer>>>() { + @Override + public ObservableSource<List<Integer>> apply(List<Integer> v) + throws Exception { + return Observable.just(v) + .subscribeOn(Schedulers.io()) + .doOnNext(new Consumer<List<Integer>>() { + @Override + public void accept(List<Integer> v) + throws Exception { + Thread.sleep(new Random().nextInt(20)); + } + }); + } + } + , 2, 3) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(list); + } + + @Test + public void arrayDelayErrorDefault() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + PublishSubject<Integer> ps3 = PublishSubject.create(); + + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Observable.concatArrayEagerDelayError(ps1, ps2, ps3) + .test(); + + to.assertEmpty(); + + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + assertTrue(ps3.hasObservers()); + + ps2.onNext(2); + ps2.onComplete(); + + to.assertEmpty(); + + ps1.onNext(1); + + to.assertValuesOnly(1); + + ps1.onComplete(); + + to.assertValuesOnly(1, 2); + + ps3.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void arrayDelayErrorMaxConcurrency() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + PublishSubject<Integer> ps3 = PublishSubject.create(); + + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Observable.concatArrayEagerDelayError(2, 2, ps1, ps2, ps3) + .test(); + + to.assertEmpty(); + + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + assertFalse(ps3.hasObservers()); + + ps2.onNext(2); + ps2.onComplete(); + + to.assertEmpty(); + + ps1.onNext(1); + + to.assertValuesOnly(1); + + ps1.onComplete(); + + assertTrue(ps3.hasObservers()); + + to.assertValuesOnly(1, 2); + + ps3.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void arrayDelayErrorMaxConcurrencyErrorDelayed() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + PublishSubject<Integer> ps3 = PublishSubject.create(); + + @SuppressWarnings("unchecked") + TestObserver<Integer> to = Observable.concatArrayEagerDelayError(2, 2, ps1, ps2, ps3) + .test(); + + to.assertEmpty(); + + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + assertFalse(ps3.hasObservers()); + + ps2.onNext(2); + ps2.onError(new TestException()); + + to.assertEmpty(); + + ps1.onNext(1); + + to.assertValuesOnly(1); + + ps1.onComplete(); + + assertTrue(ps3.hasObservers()); + + to.assertValuesOnly(1, 2); + + ps3.onComplete(); + + to.assertFailure(TestException.class, 1, 2); + } + + @Test + public void cancelActive() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + + TestObserver<Integer> to = Observable + .concatEager(Observable.just(ps1, ps2)) + .test(); + + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + + to.dispose(); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + } + + @Test + public void cancelNoInnerYet() { + PublishSubject<Observable<Integer>> ps1 = PublishSubject.create(); + + TestObserver<Integer> to = Observable + .concatEager(ps1) + .test(); + + assertTrue(ps1.hasObservers()); + + to.dispose(); + + assertFalse(ps1.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java index 9991e65254..4940d6c857 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatMapTest.java @@ -13,21 +13,21 @@ package io.reactivex.internal.operators.observable; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.*; public class ObservableConcatMapTest { @@ -232,7 +232,7 @@ public ObservableSource<Integer> apply(Integer v) throws Exception { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishSubject<Integer> ps1 = PublishSubject.create(); @@ -261,7 +261,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -432,4 +432,93 @@ public void onComplete() { assertTrue(disposable[0].isDisposed()); } + + @Test + public void reentrantNoOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.concatMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.just(v + 1); + } + }, 1) + .subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 1; i < 10; i++) { + ps.onNext(i); + } + ps.onComplete(); + } + } + }); + + ps.onNext(0); + + if (!errors.isEmpty()) { + to.onError(new CompositeException(errors)); + } + + to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void reentrantNoOverflowHidden() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.concatMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.just(v + 1).hide(); + } + }, 1) + .subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 1; i < 10; i++) { + ps.onNext(i); + } + ps.onComplete(); + } + } + }); + + ps.onNext(0); + + to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void noCancelPrevious() { + final AtomicInteger counter = new AtomicInteger(); + + Observable.range(1, 5) + .concatMap(new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer v) throws Exception { + return Observable.just(v).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java index 2a158cb714..d47c684cfc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatTest.java @@ -28,6 +28,7 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; @@ -155,7 +156,6 @@ public void testNestedAsyncConcat() throws InterruptedException { final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); - Observable<Observable<String>> observableOfObservables = Observable.unsafeCreate(new ObservableSource<Observable<String>>() { @Override @@ -393,17 +393,17 @@ public void testConcatUnsubscribe() { final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); final Observable<String> concat = Observable.concat(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); try { // Subscribe - concat.subscribe(ts); + concat.subscribe(to); //Block main thread to allow Observable "w1" to complete and Observable "w2" to call onNext once. callOnce.await(); // Unsubcribe - ts.dispose(); + to.dispose(); //Unblock the Observable to continue. okToContinue.countDown(); w1.t.join(); @@ -435,19 +435,19 @@ public void testConcatUnsubscribeConcurrent() { final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); @SuppressWarnings("unchecked") TestObservable<Observable<String>> observableOfObservables = new TestObservable<Observable<String>>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); Observable<String> concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); - concatF.subscribe(ts); + concatF.subscribe(to); try { //Block main thread to allow Observable "w1" to complete and Observable "w2" to call onNext exactly once. callOnce.await(); //"four" from w2 has been processed by onNext() - ts.dispose(); + to.dispose(); //"five" and "six" will NOT be processed by onNext() //Unblock the Observable to continue. okToContinue.countDown(); @@ -471,7 +471,7 @@ public void testConcatUnsubscribeConcurrent() { static class TestObservable<T> implements ObservableSource<T> { - private final Disposable s = new Disposable() { + private final Disposable upstream = new Disposable() { @Override public void dispose() { subscribed = false; @@ -514,7 +514,7 @@ public boolean isDisposed() { @Override public void subscribe(final Observer<? super T> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); t = new Thread(new Runnable() { @Override @@ -623,6 +623,7 @@ public Observable<Integer> apply(Integer v) { inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } + @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; @@ -663,21 +664,21 @@ public void testConcatWithNonCompliantSourceDoubleOnComplete() { Observable<String> o = Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { - s.onSubscribe(Disposables.empty()); - s.onNext("hello"); - s.onComplete(); - s.onComplete(); + public void subscribe(Observer<? super String> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext("hello"); + observer.onComplete(); + observer.onComplete(); } }); - TestObserver<String> ts = new TestObserver<String>(); - Observable.concat(o, o).subscribe(ts); - ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValues("hello", "hello"); + TestObserver<String> to = new TestObserver<String>(); + Observable.concat(o, o).subscribe(to); + to.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValues("hello", "hello"); } @Test(timeout = 30000) @@ -743,7 +744,7 @@ public void concatMapRangeAsyncLoopIssue2876() { if (i % 1000 == 0) { System.out.println("concatMapRangeAsyncLoop > " + i); } - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 1000) .concatMap(new Function<Integer, Observable<Integer>>() { @Override @@ -751,13 +752,13 @@ public Observable<Integer> apply(Integer t) { return Observable.fromIterable(Arrays.asList(t)); } }) - .observeOn(Schedulers.computation()).subscribe(ts); + .observeOn(Schedulers.computation()).subscribe(to); - ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - assertEquals(1000, ts.valueCount()); - assertEquals((Integer)999, ts.values().get(999)); + to.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); + to.assertTerminated(); + to.assertNoErrors(); + assertEquals(1000, to.valueCount()); + assertEquals((Integer)999, to.values().get(999)); } } @@ -1155,4 +1156,42 @@ public void onComplete() { assertTrue(disposable[0].isDisposed()); } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousArray() { + final AtomicInteger counter = new AtomicInteger(); + + Observable<Integer> source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Observable.concatArray(source, source, source, source, source) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void noCancelPreviousIterable() { + final AtomicInteger counter = new AtomicInteger(); + + Observable<Integer> source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + Observable.concat(Arrays.asList(source, source, source, source, source)) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletableTest.java new file mode 100644 index 0000000000..458d0320c0 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletableTest.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.CompletableSubject; + +public class ObservableConcatWithCompletableTest { + + @Test + public void normal() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .subscribe(to); + + to.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.<Integer>error(new TestException()) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .subscribe(to); + + to.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Completable.error(new TestException())) + .subscribe(to); + + to.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .take(3) + .subscribe(to); + + to.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Object> to = Observable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + to.cancel(); + + assertFalse(other.hasObservers()); + } + + @Test + public void badSource() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + Disposable bs2 = Disposables.empty(); + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onComplete(); + } + }.concatWith(Completable.complete()) + .test() + .assertResult(); + } + + @Test + public void consumerDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + assertTrue(bs1.isDisposed()); + } + }.concatWith(Completable.complete()) + .take(1) + .test() + .assertResult(1); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java new file mode 100644 index 0000000000..3d03c3d8a1 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybeTest.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.MaybeSubject; + +public class ObservableConcatWithMaybeTest { + + @Test + public void normalEmpty() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .subscribe(to); + + to.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void normalNonEmpty() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Maybe.just(100)) + .subscribe(to); + + to.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.<Integer>error(new TestException()) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .subscribe(to); + + to.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Maybe.<Integer>error(new TestException())) + .subscribe(to); + + to.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Maybe.<Integer>fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + })) + .take(3) + .subscribe(to); + + to.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + MaybeSubject<Object> other = MaybeSubject.create(); + + TestObserver<Object> to = Observable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + to.cancel(); + + assertFalse(other.hasObservers()); + } + + @Test + public void consumerDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + assertTrue(bs1.isDisposed()); + } + }.concatWith(Maybe.just(100)) + .take(1) + .test() + .assertResult(1); + } + + @Test + public void badSource() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + Disposable bs2 = Disposables.empty(); + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onComplete(); + } + }.concatWith(Maybe.<Integer>empty()) + .test() + .assertResult(); + } + + @Test + public void badSource2() { + Flowable.empty().concatWith(new Maybe<Integer>() { + @Override + protected void subscribeActual(MaybeObserver<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + Disposable bs2 = Disposables.empty(); + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onComplete(); + } + }) + .test() + .assertResult(); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingleTest.java new file mode 100644 index 0000000000..824c6ac59e --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingleTest.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.SingleSubject; + +public class ObservableConcatWithSingleTest { + + @Test + public void normal() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Single.just(100)) + .subscribe(to); + + to.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void mainError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.<Integer>error(new TestException()) + .concatWith(Single.just(100)) + .subscribe(to); + + to.assertFailure(TestException.class); + } + + @Test + public void otherError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Single.<Integer>error(new TestException())) + .subscribe(to); + + to.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void takeMain() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5) + .concatWith(Single.just(100)) + .take(3) + .subscribe(to); + + to.assertResult(1, 2, 3); + } + + @Test + public void cancelOther() { + SingleSubject<Object> other = SingleSubject.create(); + + TestObserver<Object> to = Observable.empty() + .concatWith(other) + .test(); + + assertTrue(other.hasObservers()); + + to.cancel(); + + assertFalse(other.hasObservers()); + } + + @Test + public void consumerDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + assertTrue(bs1.isDisposed()); + } + }.concatWith(Single.just(100)) + .take(1) + .test() + .assertResult(1); + } + + @Test + public void badSource() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + Disposable bs2 = Disposables.empty(); + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onComplete(); + } + }.concatWith(Single.<Integer>just(100)) + .test() + .assertResult(100); + } + + @Test + public void badSource2() { + Flowable.empty().concatWith(new Single<Integer>() { + @Override + protected void subscribeActual(SingleObserver<? super Integer> observer) { + Disposable bs1 = Disposables.empty(); + observer.onSubscribe(bs1); + + Disposable bs2 = Disposables.empty(); + observer.onSubscribe(bs2); + + assertFalse(bs1.isDisposed()); + assertTrue(bs2.isDisposed()); + + observer.onSuccess(100); + } + }) + .test() + .assertResult(100); + } + +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableCreateTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableCreateTest.java index ef623e98a4..a3e86e18e9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableCreateTest.java @@ -26,7 +26,6 @@ import io.reactivex.functions.Cancellable; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class ObservableCreateTest { @@ -438,18 +437,18 @@ public void subscribe(ObservableEmitter<Object> e) throws Exception { Runnable r1 = new Runnable() { @Override public void run() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { f.onNext(1); } } }; - TestHelper.race(r1, r1, Schedulers.single()); + TestHelper.race(r1, r1); } }) - .take(1000) + .take(TestHelper.RACE_DEFAULT_LOOPS) .test() - .assertSubscribed().assertValueCount(1000).assertComplete().assertNoErrors(); + .assertSubscribed().assertValueCount(TestHelper.RACE_DEFAULT_LOOPS).assertComplete().assertNoErrors(); } @Test @@ -478,7 +477,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }) .test() @@ -512,7 +511,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }) .test() @@ -546,14 +545,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }); List<Throwable> errors = TestHelper.trackPluginErrors(); try { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { source .test() .assertFailure(Throwable.class); @@ -585,11 +584,11 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } }); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { source .test() .assertResult(); @@ -644,4 +643,15 @@ public void subscribe(ObservableEmitter<Object> e) throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void emitterHasToString() { + Observable.create(new ObservableOnSubscribe<Object>() { + @Override + public void subscribe(ObservableEmitter<Object> emitter) throws Exception { + assertTrue(emitter.toString().contains(ObservableCreate.CreateEmitter.class.getSimpleName())); + assertTrue(emitter.serialize().toString().contains(ObservableCreate.CreateEmitter.class.getSimpleName())); + } + }).test().assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java index 39588be465..aa24f68cd9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java @@ -13,26 +13,27 @@ package io.reactivex.internal.operators.observable; - import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.InOrder; +import org.reactivestreams.Publisher; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableDebounceTimed.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.TestScheduler; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class ObservableDebounceTest { @@ -236,6 +237,7 @@ public Observable<Integer> apply(Integer t1) { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + @Test public void debounceTimedLastIsNotLost() { PublishSubject<Integer> source = PublishSubject.create(); @@ -253,6 +255,7 @@ public void debounceTimedLastIsNotLost() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void debounceSelectorLastIsNotLost() { PublishSubject<Integer> source = PublishSubject.create(); @@ -376,4 +379,139 @@ public void debounceWithEmpty() { .test() .assertResult(1); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> o) throws Exception { + return o.debounce(Functions.justFunction(Observable.never())); + } + }); + } + + @Test + public void disposeInOnNext() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + BehaviorSubject.createDefault(1) + .debounce(new Function<Integer, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Integer o) throws Exception { + to.cancel(); + return Observable.never(); + } + }) + .subscribeWith(to) + .assertEmpty(); + + assertTrue(to.isDisposed()); + } + + @Test + public void disposedInOnComplete() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + to.cancel(); + observer.onComplete(); + } + } + .debounce(Functions.justFunction(Observable.never())) + .subscribeWith(to) + .assertEmpty(); + } + + @Test + public void emitLate() { + final AtomicReference<Observer<? super Integer>> ref = new AtomicReference<Observer<? super Integer>>(); + + TestObserver<Integer> to = Observable.range(1, 2) + .debounce(new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer o) throws Exception { + if (o != 1) { + return Observable.never(); + } + return new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }; + } + }) + .test(); + + ref.get().onNext(1); + + to + .assertResult(2); + } + + @Test + public void timedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Flowable<Object> f) + throws Exception { + return f.debounce(1, TimeUnit.SECONDS); + } + }); + } + + @Test + public void timedDisposedIgnoredBySource() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + new Observable<Integer>() { + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + to.cancel(); + observer.onNext(1); + observer.onComplete(); + } + } + .debounce(1, TimeUnit.SECONDS) + .subscribe(to); + } + + @Test + public void timedLateEmit() { + TestObserver<Integer> to = new TestObserver<Integer>(); + DebounceTimedObserver<Integer> sub = new DebounceTimedObserver<Integer>( + to, 1, TimeUnit.SECONDS, new TestScheduler().createWorker()); + + sub.onSubscribe(Disposables.empty()); + + DebounceEmitter<Integer> de = new DebounceEmitter<Integer>(1, 50, sub); + de.run(); + de.run(); + + to.assertEmpty(); + } + + @Test + public void timedError() { + Observable.error(new TestException()) + .debounce(1, TimeUnit.SECONDS) + .test() + .assertFailure(TestException.class); + } + + @Test + public void debounceOnEmpty() { + Observable.empty().debounce(new Function<Object, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Object o) { + return Observable.just(new Object()); + } + }).subscribe(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDeferTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDeferTest.java index 6e83ad1956..47ca034628 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDeferTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDeferTest.java @@ -21,7 +21,6 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; -import io.reactivex.observers.DefaultObserver; @SuppressWarnings("unchecked") public class ObservableDeferTest { @@ -69,7 +68,7 @@ public void testDeferFunctionThrows() throws Exception { Observable<String> result = Observable.defer(factory); - DefaultObserver<String> o = mock(DefaultObserver.class); + Observer<String> o = TestHelper.mockObserver(); result.subscribe(o); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java index 566bf0ffb3..a37a2b00a7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOtherTest.java @@ -31,7 +31,7 @@ public class ObservableDelaySubscriptionOtherTest { public void testNoPrematureSubscription() { PublishSubject<Object> other = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger subscribed = new AtomicInteger(); @@ -43,11 +43,11 @@ public void accept(Disposable d) { } }) .delaySubscription(other) - .subscribe(ts); + .subscribe(to); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); Assert.assertEquals("Premature subscription", 0, subscribed.get()); @@ -55,16 +55,16 @@ public void accept(Disposable d) { Assert.assertEquals("No subscription", 1, subscribed.get()); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(1); + to.assertNoErrors(); + to.assertComplete(); } @Test public void testNoMultipleSubscriptions() { PublishSubject<Object> other = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger subscribed = new AtomicInteger(); @@ -76,11 +76,11 @@ public void accept(Disposable d) { } }) .delaySubscription(other) - .subscribe(ts); + .subscribe(to); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); Assert.assertEquals("Premature subscription", 0, subscribed.get()); @@ -89,16 +89,16 @@ public void accept(Disposable d) { Assert.assertEquals("No subscription", 1, subscribed.get()); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(1); + to.assertNoErrors(); + to.assertComplete(); } @Test public void testCompleteTriggersSubscription() { PublishSubject<Object> other = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger subscribed = new AtomicInteger(); @@ -110,11 +110,11 @@ public void accept(Disposable d) { } }) .delaySubscription(other) - .subscribe(ts); + .subscribe(to); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); Assert.assertEquals("Premature subscription", 0, subscribed.get()); @@ -122,16 +122,16 @@ public void accept(Disposable d) { Assert.assertEquals("No subscription", 1, subscribed.get()); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(1); + to.assertNoErrors(); + to.assertComplete(); } @Test public void testNoPrematureSubscriptionToError() { PublishSubject<Object> other = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger subscribed = new AtomicInteger(); @@ -143,11 +143,11 @@ public void accept(Disposable d) { } }) .delaySubscription(other) - .subscribe(ts); + .subscribe(to); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); Assert.assertEquals("Premature subscription", 0, subscribed.get()); @@ -155,16 +155,16 @@ public void accept(Disposable d) { Assert.assertEquals("No subscription", 1, subscribed.get()); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test public void testNoSubscriptionIfOtherErrors() { PublishSubject<Object> other = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger subscribed = new AtomicInteger(); @@ -176,11 +176,11 @@ public void accept(Disposable d) { } }) .delaySubscription(other) - .subscribe(ts); + .subscribe(to); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); Assert.assertEquals("Premature subscription", 0, subscribed.get()); @@ -188,9 +188,9 @@ public void accept(Disposable d) { Assert.assertEquals("Premature subscription", 0, subscribed.get()); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test @@ -203,7 +203,6 @@ public Object apply(Observable<Integer> o) throws Exception { }, false, 1, 1, 1); } - @Test public void afterDelayNoInterrupt() { ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java index 9073b4ee93..40e396b314 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -218,10 +217,10 @@ public void testDelaySubscriptionDisposeBeforeTime() { Observable<Integer> result = Observable.just(1, 2, 3).delaySubscription(100, TimeUnit.MILLISECONDS, scheduler); Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); - result.subscribe(ts); - ts.dispose(); + result.subscribe(to); + to.dispose(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); verify(o, never()).onNext(any()); @@ -640,7 +639,7 @@ public void accept(Notification<Integer> t1) { @Test public void testBackpressureWithTimedDelay() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, Flowable.bufferSize() * 2) .delay(100, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) @@ -659,16 +658,16 @@ public Integer apply(Integer t) { return t; } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 2, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 2, to.valueCount()); } @Test public void testBackpressureWithSubscriptionTimedDelay() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, Flowable.bufferSize() * 2) .delaySubscription(100, TimeUnit.MILLISECONDS) .delay(100, TimeUnit.MILLISECONDS) @@ -688,16 +687,16 @@ public Integer apply(Integer t) { return t; } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 2, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 2, to.valueCount()); } @Test public void testBackpressureWithSelectorDelay() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, Flowable.bufferSize() * 2) .delay(new Function<Integer, Observable<Long>>() { @@ -723,16 +722,16 @@ public Integer apply(Integer t) { return t; } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 2, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 2, to.valueCount()); } @Test public void testBackpressureWithSelectorDelayAndSubscriptionDelay() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, Flowable.bufferSize() * 2) .delay(Observable.timer(500, TimeUnit.MILLISECONDS) , new Function<Integer, Observable<Long>>() { @@ -759,11 +758,11 @@ public Integer apply(Integer t) { return t; } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 2, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 2, to.valueCount()); } @Test @@ -772,9 +771,9 @@ public void testErrorRunsBeforeOnNext() { PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - ps.delay(1, TimeUnit.SECONDS, test).subscribe(ts); + ps.delay(1, TimeUnit.SECONDS, test).subscribe(to); ps.onNext(1); @@ -784,9 +783,9 @@ public void testErrorRunsBeforeOnNext() { test.advanceTimeBy(1, TimeUnit.SECONDS); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test @@ -795,19 +794,19 @@ public void testDelaySupplierSimple() { Observable<Integer> source = Observable.range(1, 5); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.delaySubscription(ps).subscribe(ts); + source.delaySubscription(ps).subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); ps.onNext(1); - ts.assertValues(1, 2, 3, 4, 5); - ts.assertComplete(); - ts.assertNoErrors(); + to.assertValues(1, 2, 3, 4, 5); + to.assertComplete(); + to.assertNoErrors(); } @Test @@ -816,20 +815,20 @@ public void testDelaySupplierCompletes() { Observable<Integer> source = Observable.range(1, 5); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.delaySubscription(ps).subscribe(ts); + source.delaySubscription(ps).subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); // FIXME should this complete the source instead of consuming it? ps.onComplete(); - ts.assertValues(1, 2, 3, 4, 5); - ts.assertComplete(); - ts.assertNoErrors(); + to.assertValues(1, 2, 3, 4, 5); + to.assertComplete(); + to.assertNoErrors(); } @Test @@ -838,19 +837,19 @@ public void testDelaySupplierErrors() { Observable<Integer> source = Observable.range(1, 5); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.delaySubscription(ps).subscribe(ts); + source.delaySubscription(ps).subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); ps.onError(new TestException()); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java index c72ae018cc..d2c5a4ca57 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -24,11 +23,56 @@ import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; +@SuppressWarnings("deprecation") public class ObservableDematerializeTest { + @Test + public void simpleSelector() { + Observable<Notification<Integer>> notifications = Observable.just(1, 2).materialize(); + Observable<Integer> dematerialize = notifications.dematerialize(Functions.<Notification<Integer>>identity()); + + Observer<Integer> observer = TestHelper.mockObserver(); + + dematerialize.subscribe(observer); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onComplete(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void selectorCrash() { + Observable.just(1, 2) + .materialize() + .dematerialize(new Function<Notification<Integer>, Notification<Object>>() { + @Override + public Notification<Object> apply(Notification<Integer> v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Observable.just(1, 2) + .materialize() + .dematerialize(new Function<Notification<Integer>, Notification<Object>>() { + @Override + public Notification<Object> apply(Notification<Integer> v) throws Exception { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + @Test public void testDematerialize1() { Observable<Notification<Integer>> notifications = Observable.just(1, 2).materialize(); @@ -96,10 +140,10 @@ public void testCompletePassThru() { Observer<Integer> observer = TestHelper.mockObserver(); - TestObserver<Integer> ts = new TestObserver<Integer>(observer); - dematerialize.subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(observer); + dematerialize.subscribe(to); - System.out.println(ts.errors()); + System.out.println(to.errors()); verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); @@ -175,4 +219,19 @@ protected void subscribeActual(Observer<? super Object> observer) { RxJavaPlugins.reset(); } } + + @Test + public void nonNotificationInstanceAfterDispose() { + new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(Notification.createOnComplete()); + observer.onNext(1); + } + } + .dematerialize() + .test() + .assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java index 8c1ff32467..ab20fccc38 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDetachTest.java @@ -24,7 +24,6 @@ import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; - public class ObservableDetachTest { Object o; @@ -35,13 +34,13 @@ public void just() throws Exception { WeakReference<Object> wr = new WeakReference<Object>(o); - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.just(o).count().toObservable().onTerminateDetach().subscribe(ts); + Observable.just(o).count().toObservable().onTerminateDetach().subscribe(to); - ts.assertValue(1L); - ts.assertComplete(); - ts.assertNoErrors(); + to.assertValue(1L); + to.assertComplete(); + to.assertNoErrors(); o = null; @@ -54,38 +53,37 @@ public void just() throws Exception { @Test public void error() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.error(new TestException()).onTerminateDetach().subscribe(ts); + Observable.error(new TestException()).onTerminateDetach().subscribe(to); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test public void empty() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.empty().onTerminateDetach().subscribe(ts); + Observable.empty().onTerminateDetach().subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertComplete(); } @Test public void range() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.range(1, 1000).onTerminateDetach().subscribe(ts); + Observable.range(1, 1000).onTerminateDetach().subscribe(to); - ts.assertValueCount(1000); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValueCount(1000); + to.assertNoErrors(); + to.assertComplete(); } - @Test @Ignore("Observable doesn't do backpressure") public void backpressured() throws Exception { @@ -93,17 +91,17 @@ public void backpressured() throws Exception { // // WeakReference<Object> wr = new WeakReference<Object>(o); // -// TestObserver<Object> ts = new TestObserver<Object>(0L); +// TestObserver<Object> to = new TestObserver<Object>(0L); // // Observable.just(o).count().onTerminateDetach().subscribe(ts); // -// ts.assertNoValues(); +// to.assertNoValues(); // -// ts.request(1); +// to.request(1); // -// ts.assertValue(1L); -// ts.assertComplete(); -// ts.assertNoErrors(); +// to.assertValue(1L); +// to.assertComplete(); +// to.assertNoErrors(); // // o = null; // @@ -119,10 +117,10 @@ public void justUnsubscribed() throws Exception { WeakReference<Object> wr = new WeakReference<Object>(o); - TestObserver<Long> ts = Observable.just(o).count().toObservable().onTerminateDetach().test(); + TestObserver<Long> to = Observable.just(o).count().toObservable().onTerminateDetach().test(); o = null; - ts.cancel(); + to.cancel(); System.gc(); Thread.sleep(200); @@ -136,26 +134,26 @@ public void justUnsubscribed() throws Exception { public void deferredUpstreamProducer() { // final AtomicReference<Subscriber<? super Object>> subscriber = new AtomicReference<Subscriber<? super Object>>(); // -// TestObserver<Object> ts = new TestObserver<Object>(0); +// TestObserver<Object> to = new TestObserver<Object>(0); // // Observable.unsafeCreate(new ObservableSource<Object>() { // @Override // public void subscribe(Subscriber<? super Object> t) { // subscriber.set(t); // } -// }).onTerminateDetach().subscribe(ts); +// }).onTerminateDetach().subscribe(to); // -// ts.request(2); +// to.request(2); // // new ObservableRange(1, 3).subscribe(subscriber.get()); // -// ts.assertValues(1, 2); +// to.assertValues(1, 2); // -// ts.request(1); +// to.request(1); // -// ts.assertValues(1, 2, 3); -// ts.assertComplete(); -// ts.assertNoErrors(); +// to.assertValues(1, 2, 3); +// to.assertComplete(); +// to.assertNoErrors(); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java index 299a5c3010..67f73d3ede 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -30,7 +29,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.UnicastSubject; @@ -144,19 +143,19 @@ public void error() { @Test public void fusedSync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.just(1, 1, 2, 1, 3, 2, 4, 5, 4) .distinct() .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedAsync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> us = UnicastSubject.create(); @@ -166,7 +165,7 @@ public void fusedAsync() { TestHelper.emit(us, 1, 1, 2, 1, 3, 2, 4, 5, 4); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java index c519e3d036..6bd333e814 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; @@ -26,7 +25,7 @@ import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.*; @@ -140,9 +139,9 @@ public void testDistinctUntilChangedOfSourceWithExceptionsFromKeySelector() { @Test public void customComparator() { - Observable<String> source = Observable.just("a", "b", "B", "A","a", "C"); + Observable<String> source = Observable.just("a", "b", "B", "A", "a", "C"); - TestObserver<String> ts = TestObserver.create(); + TestObserver<String> to = TestObserver.create(); source.distinctUntilChanged(new BiPredicate<String, String>() { @Override @@ -150,18 +149,18 @@ public boolean test(String a, String b) { return a.compareToIgnoreCase(b) == 0; } }) - .subscribe(ts); + .subscribe(to); - ts.assertValues("a", "b", "A", "C"); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues("a", "b", "A", "C"); + to.assertNoErrors(); + to.assertComplete(); } @Test public void customComparatorThrows() { - Observable<String> source = Observable.just("a", "b", "B", "A","a", "C"); + Observable<String> source = Observable.just("a", "b", "B", "A", "a", "C"); - TestObserver<String> ts = TestObserver.create(); + TestObserver<String> to = TestObserver.create(); source.distinctUntilChanged(new BiPredicate<String, String>() { @Override @@ -169,16 +168,16 @@ public boolean test(String a, String b) { throw new TestException(); } }) - .subscribe(ts); + .subscribe(to); - ts.assertValue("a"); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertValue("a"); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test public void fused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.just(1, 2, 2, 3, 3, 4, 5) .distinctUntilChanged(new BiPredicate<Integer, Integer>() { @@ -190,14 +189,14 @@ public boolean test(Integer a, Integer b) throws Exception { .subscribe(to); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.SYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5) ; } @Test public void fusedAsync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> up = UnicastSubject.create(); @@ -213,7 +212,7 @@ public boolean test(Integer a, Integer b) throws Exception { TestHelper.emit(up, 1, 2, 2, 3, 3, 4, 5); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5) ; } @@ -225,13 +224,13 @@ public void ignoreCancel() { try { Observable.wrap(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s) { - s.onSubscribe(Disposables.empty()); - s.onNext(1); - s.onNext(2); - s.onNext(3); - s.onError(new IOException()); - s.onComplete(); + public void subscribe(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onNext(3); + observer.onError(new IOException()); + observer.onComplete(); } }) .distinctUntilChanged(new BiPredicate<Integer, Integer>() { @@ -257,9 +256,9 @@ class Mutable { public void mutableWithSelector() { Mutable m = new Mutable(); - PublishSubject<Mutable> pp = PublishSubject.create(); + PublishSubject<Mutable> ps = PublishSubject.create(); - TestObserver<Mutable> ts = pp.distinctUntilChanged(new Function<Mutable, Object>() { + TestObserver<Mutable> to = ps.distinctUntilChanged(new Function<Mutable, Object>() { @Override public Object apply(Mutable m) throws Exception { return m.value; @@ -267,11 +266,11 @@ public Object apply(Mutable m) throws Exception { }) .test(); - pp.onNext(m); + ps.onNext(m); m.value = 1; - pp.onNext(m); - pp.onComplete(); + ps.onNext(m); + ps.onComplete(); - ts.assertResult(m, m); + to.assertResult(m, m); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java index ca6280562c..a091306b9e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoAfterNextTest.java @@ -24,10 +24,9 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Consumer; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; -import io.reactivex.processors.UnicastProcessor; -import io.reactivex.subscribers.*; +import io.reactivex.subjects.UnicastSubject; public class ObservableDoAfterNextTest { @@ -40,7 +39,7 @@ public void accept(Integer e) throws Exception { } }; - final TestObserver<Integer> ts = new TestObserver<Integer>() { + final TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -52,7 +51,18 @@ public void onNext(Integer t) { public void just() { Observable.just(1) .doAfterNext(afterNext) - .subscribeWith(ts) + .subscribeWith(to) + .assertResult(1); + + assertEquals(Arrays.asList(1, -1), values); + } + + @Test + public void justHidden() { + Observable.just(1) + .hide() + .doAfterNext(afterNext) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -62,7 +72,7 @@ public void just() { public void range() { Observable.range(1, 5) .doAfterNext(afterNext) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(1, -1, 2, -2, 3, -3, 4, -4, 5, -5), values); @@ -72,7 +82,7 @@ public void range() { public void error() { Observable.<Integer>error(new TestException()) .doAfterNext(afterNext) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); @@ -82,7 +92,7 @@ public void error() { public void empty() { Observable.<Integer>empty() .doAfterNext(afterNext) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(); assertTrue(values.isEmpty()); @@ -90,13 +100,13 @@ public void empty() { @Test public void syncFused() { - TestObserver<Integer> ts0 = ObserverFusion.newTest(QueueSubscription.SYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5) .doAfterNext(afterNext) - .subscribe(ts0); + .subscribe(to0); - ObserverFusion.assertFusion(ts0, QueueSubscription.SYNC) + ObserverFusion.assertFusion(to0, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -104,13 +114,13 @@ public void syncFused() { @Test public void asyncFusedRejected() { - TestObserver<Integer> ts0 = ObserverFusion.newTest(QueueSubscription.ASYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.range(1, 5) .doAfterNext(afterNext) - .subscribe(ts0); + .subscribe(to0); - ObserverFusion.assertFusion(ts0, QueueSubscription.NONE) + ObserverFusion.assertFusion(to0, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -118,17 +128,17 @@ public void asyncFusedRejected() { @Test public void asyncFused() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.ASYNC); - UnicastProcessor<Integer> up = UnicastProcessor.create(); + UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); up .doAfterNext(afterNext) - .subscribe(ts0); + .subscribe(to0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + ObserverFusion.assertFusion(to0, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -144,7 +154,7 @@ public void justConditional() { Observable.just(1) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -155,7 +165,7 @@ public void rangeConditional() { Observable.range(1, 5) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(1, -1, 2, -2, 3, -3, 4, -4, 5, -5), values); @@ -166,7 +176,7 @@ public void errorConditional() { Observable.<Integer>error(new TestException()) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); @@ -177,7 +187,7 @@ public void emptyConditional() { Observable.<Integer>empty() .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(); assertTrue(values.isEmpty()); @@ -185,14 +195,14 @@ public void emptyConditional() { @Test public void syncFusedConditional() { - TestObserver<Integer> ts0 = ObserverFusion.newTest(QueueSubscription.SYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribe(ts0); + .subscribe(to0); - ObserverFusion.assertFusion(ts0, QueueSubscription.SYNC) + ObserverFusion.assertFusion(to0, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -200,14 +210,14 @@ public void syncFusedConditional() { @Test public void asyncFusedRejectedConditional() { - TestObserver<Integer> ts0 = ObserverFusion.newTest(QueueSubscription.ASYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.range(1, 5) .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribe(ts0); + .subscribe(to0); - ObserverFusion.assertFusion(ts0, QueueSubscription.NONE) + ObserverFusion.assertFusion(to0, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); @@ -215,18 +225,18 @@ public void asyncFusedRejectedConditional() { @Test public void asyncFusedConditional() { - TestSubscriber<Integer> ts0 = SubscriberFusion.newTest(QueueSubscription.ASYNC); + TestObserver<Integer> to0 = ObserverFusion.newTest(QueueFuseable.ASYNC); - UnicastProcessor<Integer> up = UnicastProcessor.create(); + UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); up .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) - .subscribe(ts0); + .subscribe(to0); - SubscriberFusion.assertFusion(ts0, QueueSubscription.ASYNC) + ObserverFusion.assertFusion(to0, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(Arrays.asList(-1, -2, -3, -4, -5), values); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java index 27ae321f4f..d6f08fff27 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoFinallyTest.java @@ -26,7 +26,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.UnicastSubject; @@ -99,13 +99,13 @@ public Observable<Object> apply(Observable<Object> f) throws Exception { @Test public void syncFused() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5) .doFinally(this) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -113,13 +113,13 @@ public void syncFused() { @Test public void syncFusedBoundary() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC | QueueFuseable.BOUNDARY); Observable.range(1, 5) .doFinally(this) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -127,16 +127,16 @@ public void syncFusedBoundary() { @Test public void asyncFused() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC); UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); up .doFinally(this) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -144,22 +144,21 @@ public void asyncFused() { @Test public void asyncFusedBoundary() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ASYNC | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); up .doFinally(this) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); } - @Test public void normalJustConditional() { Observable.just(1) @@ -207,14 +206,14 @@ public void normalTakeConditional() { @Test public void syncFusedConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5) .doFinally(this) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -222,13 +221,13 @@ public void syncFusedConditional() { @Test public void nonFused() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5).hide() .doFinally(this) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -236,14 +235,14 @@ public void nonFused() { @Test public void nonFusedConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5).hide() .doFinally(this) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -251,14 +250,14 @@ public void nonFusedConditional() { @Test public void syncFusedBoundaryConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.SYNC | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC | QueueFuseable.BOUNDARY); Observable.range(1, 5) .doFinally(this) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -266,7 +265,7 @@ public void syncFusedBoundaryConditional() { @Test public void asyncFusedConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC); UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -274,9 +273,9 @@ public void asyncFusedConditional() { up .doFinally(this) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -284,7 +283,7 @@ public void asyncFusedConditional() { @Test public void asyncFusedBoundaryConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ASYNC | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); UnicastSubject<Integer> up = UnicastSubject.create(); TestHelper.emit(up, 1, 2, 3, 4, 5); @@ -292,9 +291,9 @@ public void asyncFusedBoundaryConditional() { up .doFinally(this) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); assertEquals(1, calls); @@ -355,27 +354,27 @@ public void clearIsEmpty() { .subscribe(new Observer<Integer>() { @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { @SuppressWarnings("unchecked") - QueueDisposable<Integer> qs = (QueueDisposable<Integer>)s; + QueueDisposable<Integer> qd = (QueueDisposable<Integer>)d; - qs.requestFusion(QueueDisposable.ANY); + qd.requestFusion(QueueFuseable.ANY); - assertFalse(qs.isEmpty()); + assertFalse(qd.isEmpty()); try { - assertEquals(1, qs.poll().intValue()); + assertEquals(1, qd.poll().intValue()); } catch (Throwable ex) { throw new RuntimeException(ex); } - assertFalse(qs.isEmpty()); + assertFalse(qd.isEmpty()); - qs.clear(); + qd.clear(); - assertTrue(qs.isEmpty()); + assertTrue(qd.isEmpty()); - qs.dispose(); + qd.dispose(); } @Override @@ -402,31 +401,31 @@ public void clearIsEmptyConditional() { .subscribe(new Observer<Integer>() { @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { @SuppressWarnings("unchecked") - QueueDisposable<Integer> qs = (QueueDisposable<Integer>)s; + QueueDisposable<Integer> qd = (QueueDisposable<Integer>)d; - qs.requestFusion(QueueDisposable.ANY); + qd.requestFusion(QueueFuseable.ANY); - assertFalse(qs.isEmpty()); + assertFalse(qd.isEmpty()); - assertFalse(qs.isDisposed()); + assertFalse(qd.isDisposed()); try { - assertEquals(1, qs.poll().intValue()); + assertEquals(1, qd.poll().intValue()); } catch (Throwable ex) { throw new RuntimeException(ex); } - assertFalse(qs.isEmpty()); + assertFalse(qd.isEmpty()); - qs.clear(); + qd.clear(); - assertTrue(qs.isEmpty()); + assertTrue(qd.isEmpty()); - qs.dispose(); + qd.dispose(); - assertTrue(qs.isDisposed()); + assertTrue(qd.isDisposed()); } @Override @@ -445,7 +444,6 @@ public void onComplete() { assertEquals(1, calls); } - @Test public void eventOrdering() { final List<String> list = new ArrayList<String>(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java index e1640677a7..a22c969c9b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -28,7 +27,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.UnicastSubject; @@ -201,7 +200,7 @@ public void accept(List<Boolean> booleans) { @Test public void onErrorThrows() { - TestObserver<Object> ts = TestObserver.create(); + TestObserver<Object> to = TestObserver.create(); Observable.error(new TestException()) .doOnError(new Consumer<Throwable>() { @@ -209,13 +208,13 @@ public void onErrorThrows() { public void accept(Throwable e) { throw new TestException(); } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(CompositeException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(CompositeException.class); - CompositeException ex = (CompositeException)ts.errors().get(0); + CompositeException ex = (CompositeException)to.errors().get(0); List<Throwable> exceptions = ex.getExceptions(); assertEquals(2, exceptions.size()); @@ -230,12 +229,12 @@ public void ignoreCancel() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onNext(1); - s.onNext(2); - s.onError(new IOException()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new IOException()); + observer.onComplete(); } }) .doOnNext(new Consumer<Object>() { @@ -260,9 +259,9 @@ public void onErrorAfterCrash() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onError(new TestException()); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new TestException()); } }) .doAfterTerminate(new Action() { @@ -287,9 +286,9 @@ public void onCompleteAfterCrash() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); } }) .doAfterTerminate(new Action() { @@ -311,9 +310,9 @@ public void run() throws Exception { public void onCompleteCrash() { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); } }) .doOnComplete(new Action() { @@ -333,12 +332,12 @@ public void ignoreCancelConditional() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onNext(1); - s.onNext(2); - s.onError(new IOException()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new IOException()); + observer.onComplete(); } }) .doOnNext(new Consumer<Object>() { @@ -364,9 +363,9 @@ public void onErrorAfterCrashConditional() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onError(new TestException()); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new TestException()); } }) .doAfterTerminate(new Action() { @@ -408,9 +407,9 @@ public void onCompleteAfterCrashConditional() { try { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); } }) .doAfterTerminate(new Action() { @@ -433,9 +432,9 @@ public void run() throws Exception { public void onCompleteCrashConditional() { Observable.wrap(new ObservableSource<Object>() { @Override - public void subscribe(Observer<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onComplete(); + public void subscribe(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); } }) .doOnComplete(new Action() { @@ -451,7 +450,7 @@ public void run() throws Exception { @Test public void onErrorOnErrorCrashConditional() { - TestObserver<Object> ts = Observable.error(new TestException("Outer")) + TestObserver<Object> to = Observable.error(new TestException("Outer")) .doOnError(new Consumer<Throwable>() { @Override public void accept(Throwable e) throws Exception { @@ -462,7 +461,7 @@ public void accept(Throwable e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> errors = TestHelper.compositeList(ts.errors().get(0)); + List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "Outer"); TestHelper.assertError(errors, 1, TestException.class, "Inner"); @@ -471,7 +470,7 @@ public void accept(Throwable e) throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fused() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -488,10 +487,10 @@ public void run() throws Exception { call[1]++; } }) - .subscribe(ts); + .subscribe(to); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -501,7 +500,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedOnErrorCrash() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0 }; @@ -518,10 +517,10 @@ public void run() throws Exception { call[0]++; } }) - .subscribe(ts); + .subscribe(to); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertFailure(TestException.class); assertEquals(0, call[0]); @@ -530,7 +529,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -548,10 +547,10 @@ public void run() throws Exception { } }) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -561,7 +560,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedOnErrorCrashConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0 }; @@ -579,10 +578,10 @@ public void run() throws Exception { } }) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertFailure(TestException.class); assertEquals(0, call[0]); @@ -591,7 +590,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedAsync() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -610,12 +609,12 @@ public void run() throws Exception { call[1]++; } }) - .subscribe(ts); + .subscribe(to); TestHelper.emit(up, 1, 2, 3, 4, 5); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -625,7 +624,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedAsyncConditional() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -645,12 +644,12 @@ public void run() throws Exception { } }) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); TestHelper.emit(up, 1, 2, 3, 4, 5); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); @@ -660,7 +659,7 @@ public void run() throws Exception { @Test @Ignore("Fusion not supported yet") // TODO decide/implement fusion public void fusedAsyncConditional2() { - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); final int[] call = { 0, 0 }; @@ -680,12 +679,12 @@ public void run() throws Exception { } }) .filter(Functions.alwaysTrue()) - .subscribe(ts); + .subscribe(to); TestHelper.emit(up, 1, 2, 3, 4, 5); - ts.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + to.assertOf(ObserverFusion.<Integer>assertFuseable()) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2, 3, 4, 5); assertEquals(5, call[0]); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnSubscribeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnSubscribeTest.java index dbd4ca88eb..76b9737959 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnSubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnSubscribeTest.java @@ -33,7 +33,7 @@ public void testDoOnSubscribe() throws Exception { final AtomicInteger count = new AtomicInteger(); Observable<Integer> o = Observable.just(1).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { count.incrementAndGet(); } }); @@ -49,12 +49,12 @@ public void testDoOnSubscribe2() throws Exception { final AtomicInteger count = new AtomicInteger(); Observable<Integer> o = Observable.just(1).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { count.incrementAndGet(); } }).take(1).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { count.incrementAndGet(); } }); @@ -72,21 +72,21 @@ public void testDoOnUnSubscribeWorksWithRefCount() throws Exception { Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); onSubscribed.incrementAndGet(); - sref.set(s); + sref.set(observer); } }).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { countBefore.incrementAndGet(); } }).publish().refCount() .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { countAfter.incrementAndGet(); } }); @@ -114,15 +114,15 @@ public void onSubscribeCrash() { new Observable<Integer>() { @Override - protected void subscribeActual(Observer<? super Integer> s) { - s.onSubscribe(bs); - s.onError(new TestException("Second")); - s.onComplete(); + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(bs); + observer.onError(new TestException("Second")); + observer.onComplete(); } } .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException("First"); } }) diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java index 9d2c84db3c..b7df811bac 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnUnsubscribeTest.java @@ -25,6 +25,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.BehaviorSubject; public class ObservableDoOnUnsubscribeTest { @@ -152,4 +153,24 @@ public void run() { assertEquals("There should exactly 1 un-subscription events for upper stream", 1, upperCount.get()); assertEquals("There should exactly 1 un-subscription events for lower stream", 1, lowerCount.get()); } + + @Test + public void noReentrantDispose() { + + final AtomicInteger disposeCalled = new AtomicInteger(); + + final BehaviorSubject<Integer> s = BehaviorSubject.create(); + s.doOnDispose(new Action() { + @Override + public void run() throws Exception { + disposeCalled.incrementAndGet(); + s.onNext(2); + } + }) + .firstOrError() + .subscribe() + .dispose(); + + assertEquals(1, disposeCalled.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java index 0ddb43e8b6..7ac6418db7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import org.junit.Test; @@ -23,7 +22,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.subjects.UnicastSubject; @@ -94,7 +93,7 @@ public ObservableSource<Object> apply(Observable<Object> o) throws Exception { @Test public void fusedSync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.range(1, 5) .filter(new Predicate<Integer>() { @@ -105,13 +104,13 @@ public boolean test(Integer v) throws Exception { }) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(2, 4); } @Test public void fusedAsync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> us = UnicastSubject.create(); @@ -126,13 +125,13 @@ public boolean test(Integer v) throws Exception { TestHelper.emit(us, 1, 2, 3, 4, 5); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .assertResult(2, 4); } @Test public void fusedReject() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY | QueueFuseable.BOUNDARY); Observable.range(1, 5) .filter(new Predicate<Integer>() { @@ -143,7 +142,7 @@ public boolean test(Integer v) throws Exception { }) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(2, 4); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java index 421c881c26..bd7dc12cf6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableTest.java @@ -24,7 +24,7 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -166,10 +166,9 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fusedObservable() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.range(1, 10) .flatMapCompletable(new Function<Integer, CompletableSource>() { @@ -182,7 +181,7 @@ public CompletableSource apply(Integer v) throws Exception { to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -332,10 +331,9 @@ public CompletableSource apply(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void fused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.range(1, 10) .flatMapCompletable(new Function<Integer, CompletableSource>() { @@ -349,7 +347,7 @@ public CompletableSource apply(Integer v) throws Exception { to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(); } @@ -372,14 +370,14 @@ public void innerObserver() { public CompletableSource apply(Integer v) throws Exception { return new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); - assertFalse(((Disposable)s).isDisposed()); + assertFalse(((Disposable)observer).isDisposed()); - ((Disposable)s).dispose(); + ((Disposable)observer).dispose(); - assertTrue(((Disposable)s).isDisposed()); + assertTrue(((Disposable)observer).isDisposed()); } }; } @@ -447,14 +445,14 @@ public void innerObserverObservable() { public CompletableSource apply(Integer v) throws Exception { return new Completable() { @Override - protected void subscribeActual(CompletableObserver s) { - s.onSubscribe(Disposables.empty()); + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(Disposables.empty()); - assertFalse(((Disposable)s).isDisposed()); + assertFalse(((Disposable)observer).isDisposed()); - ((Disposable)s).dispose(); + ((Disposable)observer).dispose(); - assertTrue(((Disposable)s).isDisposed()); + assertTrue(((Disposable)observer).isDisposed()); } }; } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java index d2f77576a3..56ecedc02b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybeTest.java @@ -188,7 +188,7 @@ public MaybeSource<Integer> apply(Integer v) throws Exception { @Test public void middleError() { - Observable.fromArray(new String[]{"1","a","2"}).flatMapMaybe(new Function<String, MaybeSource<Integer>>() { + Observable.fromArray(new String[]{"1", "a", "2"}).flatMapMaybe(new Function<String, MaybeSource<Integer>>() { @Override public MaybeSource<Integer> apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java index 4a56c61795..5226fc594c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingleTest.java @@ -175,7 +175,7 @@ public SingleSource<Integer> apply(Integer v) throws Exception { @Test public void middleError() { - Observable.fromArray(new String[]{"1","a","2"}).flatMapSingle(new Function<String, SingleSource<Integer>>() { + Observable.fromArray(new String[]{"1", "a", "2"}).flatMapSingle(new Function<String, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(final String s) throws NumberFormatException { //return Single.just(Integer.valueOf(s)); //This works diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java index 64cb2e7e41..61fbd21c7f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -26,14 +25,14 @@ import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class ObservableFlatMapTest { @Test @@ -205,7 +204,6 @@ public void testFlatMapTransformsException() { Observable.<Integer> error(new RuntimeException("Forced failure!")) ); - Observer<Object> o = TestHelper.mockObserver(); source.flatMap(just(onNext), just(onError), just0(onComplete)).subscribe(o); @@ -308,7 +306,7 @@ public void testFlatMapTransformsMergeException() { private static <T> Observable<T> composer(Observable<T> source, final AtomicInteger subscriptionCount, final int m) { return source.doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { int n = subscriptionCount.getAndIncrement(); if (n >= m) { Assert.fail("Too many subscriptions! " + (n + 1)); @@ -338,18 +336,19 @@ public Observable<Integer> apply(Integer t1) { } }, m); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.subscribe(ts); + source.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + to.awaitTerminalEvent(); + to.assertNoErrors(); Set<Integer> expected = new HashSet<Integer>(Arrays.asList( 10, 11, 20, 21, 30, 31, 40, 41, 50, 51, 60, 61, 70, 71, 80, 81, 90, 91, 100, 101 )); - Assert.assertEquals(expected.size(), ts.valueCount()); - Assert.assertTrue(expected.containsAll(ts.values())); + Assert.assertEquals(expected.size(), to.valueCount()); + Assert.assertTrue(expected.containsAll(to.values())); } + @Test public void testFlatMapSelectorMaxConcurrent() { final int m = 4; @@ -368,19 +367,19 @@ public Integer apply(Integer t1, Integer t2) { } }, m); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.subscribe(ts); + source.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + to.awaitTerminalEvent(); + to.assertNoErrors(); Set<Integer> expected = new HashSet<Integer>(Arrays.asList( 1010, 1011, 2020, 2021, 3030, 3031, 4040, 4041, 5050, 5051, 6060, 6061, 7070, 7071, 8080, 8081, 9090, 9091, 10100, 10101 )); - Assert.assertEquals(expected.size(), ts.valueCount()); - System.out.println("--> testFlatMapSelectorMaxConcurrent: " + ts.values()); - Assert.assertTrue(expected.containsAll(ts.values())); + Assert.assertEquals(expected.size(), to.valueCount()); + System.out.println("--> testFlatMapSelectorMaxConcurrent: " + to.values()); + Assert.assertTrue(expected.containsAll(to.values())); } @Test @@ -414,14 +413,14 @@ public void testFlatMapTransformsMaxConcurrentNormal() { Observable<Integer> source = Observable.fromIterable(Arrays.asList(10, 20, 30)); Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); Function<Throwable, Observable<Integer>> just = just(onError); - source.flatMap(just(onNext), just, just0(onComplete), m).subscribe(ts); + source.flatMap(just(onNext), just, just0(onComplete), m).subscribe(to); - ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertTerminated(); + to.awaitTerminalEvent(1, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertTerminated(); verify(o, times(3)).onNext(1); verify(o, times(3)).onNext(2); @@ -440,7 +439,7 @@ public void flatMapRangeAsyncLoop() { if (i % 10 == 0) { System.out.println("flatMapRangeAsyncLoop > " + i); } - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 1000) .flatMap(new Function<Integer, Observable<Integer>>() { @Override @@ -449,15 +448,15 @@ public Observable<Integer> apply(Integer t) { } }) .observeOn(Schedulers.computation()) - .subscribe(ts); + .subscribe(to); - ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); - if (ts.completions() == 0) { - System.out.println(ts.valueCount()); + to.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); + if (to.completions() == 0) { + System.out.println(to.valueCount()); } - ts.assertTerminated(); - ts.assertNoErrors(); - List<Integer> list = ts.values(); + to.assertTerminated(); + to.assertNoErrors(); + List<Integer> list = to.values(); assertEquals(1000, list.size()); boolean f = false; for (int j = 0; j < list.size(); j++) { @@ -471,13 +470,14 @@ public Observable<Integer> apply(Integer t) { } } } + @Test(timeout = 30000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { if (i % 10 == 0) { System.out.println("flatMapRangeAsyncLoop > " + i); } - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 1000) .flatMap(new Function<Integer, Observable<Integer>>() { final Random rnd = new Random(); @@ -491,15 +491,15 @@ public Observable<Integer> apply(Integer t) { } }) .observeOn(Schedulers.computation()) - .subscribe(ts); + .subscribe(to); - ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); - if (ts.completions() == 0) { - System.out.println(ts.valueCount()); + to.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); + if (to.completions() == 0) { + System.out.println(to.valueCount()); } - ts.assertTerminated(); - ts.assertNoErrors(); - List<Integer> list = ts.values(); + to.assertTerminated(); + to.assertNoErrors(); + List<Integer> list = to.values(); if (list.size() < 1000) { Set<Integer> set = new HashSet<Integer>(list); for (int j = 0; j < 1000; j++) { @@ -514,38 +514,39 @@ public Observable<Integer> apply(Integer t) { @Test public void flatMapIntPassthruAsync() { - for (int i = 0;i < 1000; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < 1000; i++) { + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, 1000).flatMap(new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { return Observable.just(1).subscribeOn(Schedulers.computation()); } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertComplete(); - ts.assertValueCount(1000); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertComplete(); + to.assertValueCount(1000); } } + @Test public void flatMapTwoNestedSync() { for (final int n : new int[] { 1, 1000, 1000000 }) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.just(1, 2).flatMap(new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { return Observable.range(1, n); } - }).subscribe(ts); + }).subscribe(to); System.out.println("flatMapTwoNestedSync >> @ " + n); - ts.assertNoErrors(); - ts.assertComplete(); - ts.assertValueCount(n * 2); + to.assertNoErrors(); + to.assertComplete(); + to.assertValueCount(n * 2); } } @@ -694,7 +695,7 @@ public void onNext(Integer t) { @Test public void innerCompleteCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Integer> to = Observable.merge(Observable.just(ps)).test(); @@ -762,7 +763,7 @@ public Integer apply(Integer w) throws Exception { @Test public void noCrossBoundaryFusion() { for (int i = 0; i < 500; i++) { - TestObserver<Object> ts = Observable.merge( + TestObserver<Object> to = Observable.merge( Observable.just(1).observeOn(Schedulers.single()).map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { @@ -780,7 +781,7 @@ public Object apply(Integer v) throws Exception { .awaitDone(5, TimeUnit.SECONDS) .assertValueCount(2); - List<Object> list = ts.values(); + List<Object> list = to.values(); assertTrue(list.toString(), list.contains("RxSi")); assertTrue(list.toString(), list.contains("RxCo")); @@ -789,24 +790,24 @@ public Object apply(Integer v) throws Exception { @Test public void cancelScalarDrainRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishSubject<Observable<Integer>> pp = PublishSubject.create(); + final PublishSubject<Observable<Integer>> ps = PublishSubject.create(); - final TestObserver<Integer> ts = pp.flatMap(Functions.<Observable<Integer>>identity()).test(); + final TestObserver<Integer> to = ps.flatMap(Functions.<Observable<Integer>>identity()).test(); Runnable r1 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; @@ -821,25 +822,25 @@ public void run() { @Test public void cancelDrainRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { for (int j = 1; j < 50; j += 5) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishSubject<Observable<Integer>> pp = PublishSubject.create(); + final PublishSubject<Observable<Integer>> ps = PublishSubject.create(); - final TestObserver<Integer> ts = pp.flatMap(Functions.<Observable<Integer>>identity()).test(); + final TestObserver<Integer> to = ps.flatMap(Functions.<Observable<Integer>>identity()).test(); final PublishSubject<Integer> just = PublishSubject.create(); final PublishSubject<Integer> just2 = PublishSubject.create(); - pp.onNext(just); - pp.onNext(just2); + ps.onNext(just); + ps.onNext(just2); Runnable r1 = new Runnable() { @Override public void run() { just2.onNext(1); - ts.cancel(); + to.cancel(); } }; Runnable r2 = new Runnable() { @@ -894,4 +895,250 @@ public Object apply(Integer v, Object w) throws Exception { .test() .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null ObservableSource"); } + + @Test + public void failingFusedInnerCancelsSource() { + final AtomicInteger counter = new AtomicInteger(); + Observable.range(1, 5) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .flatMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.<Integer>fromIterable(new Iterable<Integer>() { + @Override + public Iterator<Integer> iterator() { + return new Iterator<Integer>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }); + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } + + @Test + public void scalarQueueNoOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.flatMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.just(v + 1); + } + }, 1) + .subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 1; i < 10; i++) { + ps.onNext(i); + } + ps.onComplete(); + } + } + }); + + ps.onNext(0); + + if (!errors.isEmpty()) { + to.onError(new CompositeException(errors)); + } + + to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void scalarQueueNoOverflowHidden() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.flatMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.just(v + 1).hide(); + } + }, 1) + .subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + for (int i = 1; i < 10; i++) { + ps.onNext(i); + } + ps.onComplete(); + } + } + }); + + ps.onNext(0); + + to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void fusedSourceCrashResumeWithNextSource() { + final UnicastSubject<Integer> fusedSource = UnicastSubject.create(); + TestObserver<Integer> to = new TestObserver<Integer>(); + + ObservableFlatMap.MergeObserver<Integer, Integer> merger = + new ObservableFlatMap.MergeObserver<Integer, Integer>(to, new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer t) + throws Exception { + if (t == 0) { + return fusedSource + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) + throws Exception { throw new TestException(); } + }) + .compose(TestHelper.<Integer>observableStripBoundary()); + } + return Observable.range(10 * t, 5); + } + }, true, Integer.MAX_VALUE, 128); + + merger.onSubscribe(Disposables.empty()); + merger.getAndIncrement(); + + merger.onNext(0); + merger.onNext(1); + merger.onNext(2); + + assertTrue(fusedSource.hasObservers()); + + fusedSource.onNext(-1); + + merger.drainLoop(); + + to.assertValuesOnly(10, 11, 12, 13, 14, 20, 21, 22, 23, 24); + } + + @Test + public void maxConcurrencySustained() { + final PublishSubject<Integer> ps1 = PublishSubject.create(); + final PublishSubject<Integer> ps2 = PublishSubject.create(); + PublishSubject<Integer> ps3 = PublishSubject.create(); + PublishSubject<Integer> ps4 = PublishSubject.create(); + + TestObserver<Integer> to = Observable.just(ps1, ps2, ps3, ps4) + .flatMap(new Function<PublishSubject<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(PublishSubject<Integer> v) throws Exception { + return v; + } + }, 2) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + if (v == 1) { + // this will make sure the drain loop detects two completed + // inner sources and replaces them with fresh ones + ps1.onComplete(); + ps2.onComplete(); + } + } + }) + .test(); + + ps1.onNext(1); + + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + assertTrue(ps3.hasObservers()); + assertTrue(ps4.hasObservers()); + + to.dispose(); + + assertFalse(ps3.hasObservers()); + assertFalse(ps4.hasObservers()); + } + + @Test + public void mainErrorsInnerCancelled() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + + ps1 + .flatMap(Functions.justFunction(ps2)) + .test(); + + ps1.onNext(1); + assertTrue("No subscribers?", ps2.hasObservers()); + + ps1.onError(new TestException()); + + assertFalse("Has subscribers?", ps2.hasObservers()); + } + + @Test + public void innerErrorsMainCancelled() { + PublishSubject<Integer> ps1 = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); + + ps1 + .flatMap(Functions.justFunction(ps2)) + .test(); + + ps1.onNext(1); + assertTrue("No subscribers?", ps2.hasObservers()); + + ps2.onError(new TestException()); + + assertFalse("Has subscribers?", ps1.hasObservers()); + } + + @Test(timeout = 5000) + public void mixedScalarAsync() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + Observable + .range(0, 20) + .flatMap(new Function<Integer, ObservableSource<?>>() { + @Override + public ObservableSource<?> apply(Integer integer) throws Exception { + if (integer % 5 != 0) { + return Observable + .just(integer); + } + + return Observable + .just(-integer) + .observeOn(Schedulers.computation()); + } + }, false, 1) + .ignoreElements() + .blockingAwait(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlattenIterableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlattenIterableTest.java index 147ede1264..8b870877e4 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlattenIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlattenIterableTest.java @@ -13,12 +13,17 @@ package io.reactivex.internal.operators.observable; -import java.util.Arrays; +import static org.junit.Assert.assertEquals; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import io.reactivex.*; -import io.reactivex.functions.Function; +import io.reactivex.Observable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; import io.reactivex.subjects.PublishSubject; public class ObservableFlattenIterableTest { @@ -47,4 +52,47 @@ public Iterable<Integer> apply(Object v) throws Exception { } }, false, 1, 1, 10, 20); } + + @Test + public void failingInnerCancelsSource() { + final AtomicInteger counter = new AtomicInteger(); + Observable.range(1, 5) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .flatMapIterable(new Function<Integer, Iterable<Integer>>() { + @Override + public Iterable<Integer> apply(Integer v) + throws Exception { + return new Iterable<Integer>() { + @Override + public Iterator<Integer> iterator() { + return new Iterator<Integer>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + throw new TestException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + }) + .test() + .assertFailure(TestException.class); + + assertEquals(1, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java new file mode 100644 index 0000000000..c10d53db36 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java @@ -0,0 +1,312 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.concurrent.*; + +import io.reactivex.exceptions.TestException; +import io.reactivex.plugins.RxJavaPlugins; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; + +public class ObservableFromCallableTest { + + @SuppressWarnings("unchecked") + @Test + public void shouldNotInvokeFuncUntilSubscription() throws Exception { + Callable<Object> func = mock(Callable.class); + + when(func.call()).thenReturn(new Object()); + + Observable<Object> fromCallableObservable = Observable.fromCallable(func); + + verifyZeroInteractions(func); + + fromCallableObservable.subscribe(); + + verify(func).call(); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldCallOnNextAndOnCompleted() throws Exception { + Callable<String> func = mock(Callable.class); + + when(func.call()).thenReturn("test_value"); + + Observable<String> fromCallableObservable = Observable.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + fromCallableObservable.subscribe(observer); + + verify(observer).onNext("test_value"); + verify(observer).onComplete(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldCallOnError() throws Exception { + Callable<Object> func = mock(Callable.class); + + Throwable throwable = new IllegalStateException("Test exception"); + when(func.call()).thenThrow(throwable); + + Observable<Object> fromCallableObservable = Observable.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + fromCallableObservable.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onComplete(); + verify(observer).onError(throwable); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldNotDeliverResultIfSubscriberUnsubscribedBeforeEmission() throws Exception { + Callable<String> func = mock(Callable.class); + + final CountDownLatch funcLatch = new CountDownLatch(1); + final CountDownLatch observerLatch = new CountDownLatch(1); + + when(func.call()).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + observerLatch.countDown(); + + try { + funcLatch.await(); + } catch (InterruptedException e) { + // It's okay, unsubscription causes Thread interruption + + // Restoring interruption status of the Thread + Thread.currentThread().interrupt(); + } + + return "should_not_be_delivered"; + } + }); + + Observable<String> fromCallableObservable = Observable.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + TestObserver<String> outer = new TestObserver<String>(observer); + + fromCallableObservable + .subscribeOn(Schedulers.computation()) + .subscribe(outer); + + // Wait until func will be invoked + observerLatch.await(); + + // Unsubscribing before emission + outer.cancel(); + + // Emitting result + funcLatch.countDown(); + + // func must be invoked + verify(func).call(); + + // Observer must not be notified at all + verify(observer).onSubscribe(any(Disposable.class)); + verifyNoMoreInteractions(observer); + } + + @Test + public void shouldAllowToThrowCheckedException() { + final Exception checkedException = new Exception("test exception"); + + Observable<Object> fromCallableObservable = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + throw checkedException; + } + }); + + Observer<Object> observer = TestHelper.mockObserver(); + + fromCallableObservable.subscribe(observer); + + verify(observer).onSubscribe(any(Disposable.class)); + verify(observer).onError(checkedException); + verifyNoMoreInteractions(observer); + } + + @Test + public void fusedFlatMapExecution() { + final int[] calls = { 0 }; + + Observable.just(1).flatMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) + throws Exception { + return Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return ++calls[0]; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(1, calls[0]); + } + + @Test + public void fusedFlatMapExecutionHidden() { + final int[] calls = { 0 }; + + Observable.just(1).hide().flatMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) + throws Exception { + return Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return ++calls[0]; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(1, calls[0]); + } + + @Test + public void fusedFlatMapNull() { + Observable.just(1).flatMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) + throws Exception { + return Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return null; + } + }); + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void fusedFlatMapNullHidden() { + Observable.just(1).hide().flatMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) + throws Exception { + return Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return null; + } + }); + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void disposedOnArrival() { + final int[] count = { 0 }; + Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + count[0]++; + return 1; + } + }) + .test(true) + .assertEmpty(); + + assertEquals(0, count[0]); + } + + @Test + public void disposedOnCall() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + to.cancel(); + return 1; + } + }) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void disposedOnCallThrows() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + to.cancel(); + throw new TestException(); + } + }) + .subscribe(to); + + to.assertEmpty(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void take() { + Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return 1; + } + }) + .take(1) + .test() + .assertResult(1); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCompletableTest.java deleted file mode 100644 index 0defa55503..0000000000 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCompletableTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.operators.observable; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.concurrent.Callable; - -import org.junit.Test; - -import io.reactivex.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.observers.TestObserver; -import io.reactivex.plugins.RxJavaPlugins; - -public class ObservableFromCompletableTest { - - @Test - public void disposedOnArrival() { - final int[] count = { 0 }; - Observable.fromCallable(new Callable<Object>() { - @Override - public Object call() throws Exception { - count[0]++; - return 1; - } - }) - .test(true) - .assertEmpty(); - - assertEquals(0, count[0]); - } - - @Test - public void disposedOnCall() { - final TestObserver<Integer> to = new TestObserver<Integer>(); - - Observable.fromCallable(new Callable<Integer>() { - @Override - public Integer call() throws Exception { - to.cancel(); - return 1; - } - }) - .subscribe(to); - - to.assertEmpty(); - } - - @Test - public void disposedOnCallThrows() { - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - final TestObserver<Integer> to = new TestObserver<Integer>(); - - Observable.fromCallable(new Callable<Integer>() { - @Override - public Integer call() throws Exception { - to.cancel(); - throw new TestException(); - } - }) - .subscribe(to); - - to.assertEmpty(); - - TestHelper.assertUndeliverable(errors, 0, TestException.class); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void take() { - Observable.fromCallable(new Callable<Object>() { - @Override - public Object call() throws Exception { - return 1; - } - }) - .take(1) - .test() - .assertResult(1); - } -} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java index 5b121664c9..5ed25eb269 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -29,7 +28,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.util.CrashingIterable; import io.reactivex.observers.*; @@ -118,12 +117,12 @@ public void testObservableFromIterable() { public void testNoBackpressure() { Observable<Integer> o = Observable.fromIterable(Arrays.asList(1, 2, 3, 4, 5)); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - o.subscribe(ts); + o.subscribe(to); - ts.assertValues(1, 2, 3, 4, 5); - ts.assertTerminated(); + to.assertValues(1, 2, 3, 4, 5); + to.assertTerminated(); } @Test @@ -131,13 +130,13 @@ public void testSubscribeMultipleTimes() { Observable<Integer> o = Observable.fromIterable(Arrays.asList(1, 2, 3)); for (int i = 0; i < 10; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - o.subscribe(ts); + o.subscribe(to); - ts.assertValues(1, 2, 3); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues(1, 2, 3); + to.assertNoErrors(); + to.assertComplete(); } } @@ -302,12 +301,12 @@ public void remove() { @Test public void fusionRejected() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.fromIterable(Arrays.asList(1, 2, 3)) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3); } @@ -320,7 +319,7 @@ public void onSubscribe(Disposable d) { @SuppressWarnings("unchecked") QueueDisposable<Integer> qd = (QueueDisposable<Integer>)d; - qd.requestFusion(QueueDisposable.ANY); + qd.requestFusion(QueueFuseable.ANY); try { assertEquals(1, qd.poll().intValue()); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromTest.java index f39ef54d84..586480cc5b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromTest.java @@ -77,12 +77,12 @@ public ObservableSource<Object> apply(Flowable<Object> f) throws Exception { @Test public void fusionRejected() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.fromArray(1, 2, 3) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java index 3ced845ea4..31bb5cf6a0 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java @@ -993,10 +993,8 @@ public void testGroupByOnAsynchronousSourceAcceptsMultipleSubscriptions() throws Observable<GroupedObservable<Boolean, Long>> stream = source.groupBy(IS_EVEN); // create two observers - @SuppressWarnings("unchecked") - DefaultObserver<GroupedObservable<Boolean, Long>> o1 = mock(DefaultObserver.class); - @SuppressWarnings("unchecked") - DefaultObserver<GroupedObservable<Boolean, Long>> o2 = mock(DefaultObserver.class); + Observer<GroupedObservable<Boolean, Long>> o1 = TestHelper.mockObserver(); + Observer<GroupedObservable<Boolean, Long>> o2 = TestHelper.mockObserver(); // subscribe with the observers stream.subscribe(o1); @@ -1026,7 +1024,7 @@ public Boolean apply(Integer n) { @Test public void testGroupByBackpressure() throws InterruptedException { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.range(1, 4000) .groupBy(IS_EVEN2) @@ -1052,9 +1050,9 @@ public String apply(Integer l) { }); } - }).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } <T, R> Function<T, R> just(final R value) { @@ -1153,12 +1151,12 @@ public String apply(String v) { } }); - TestObserver<String> ts = new TestObserver<String>(); - m.subscribe(ts); - ts.awaitTerminalEvent(); - System.out.println("ts .get " + ts.values()); - ts.assertNoErrors(); - assertEquals(ts.values(), + TestObserver<String> to = new TestObserver<String>(); + m.subscribe(to); + to.awaitTerminalEvent(); + System.out.println("ts .get " + to.values()); + to.assertNoErrors(); + assertEquals(to.values(), Arrays.asList("foo-0", "foo-1", "bar-0", "foo-0", "baz-0", "qux-0", "bar-1", "bar-0", "foo-1", "baz-1", "baz-0", "foo-0")); } @@ -1169,11 +1167,11 @@ public void keySelectorThrows() { Observable<Integer> m = source.groupBy(fail(0), dbl).flatMap(FLATTEN_INTEGER); - TestObserver<Integer> ts = new TestObserver<Integer>(); - m.subscribe(ts); - ts.awaitTerminalEvent(); - assertEquals(1, ts.errorCount()); - ts.assertNoValues(); + TestObserver<Integer> to = new TestObserver<Integer>(); + m.subscribe(to); + to.awaitTerminalEvent(); + assertEquals(1, to.errorCount()); + to.assertNoValues(); } @Test @@ -1181,11 +1179,11 @@ public void valueSelectorThrows() { Observable<Integer> source = Observable.just(0, 1, 2, 3, 4, 5, 6); Observable<Integer> m = source.groupBy(identity, fail(0)).flatMap(FLATTEN_INTEGER); - TestObserver<Integer> ts = new TestObserver<Integer>(); - m.subscribe(ts); - ts.awaitTerminalEvent(); - assertEquals(1, ts.errorCount()); - ts.assertNoValues(); + TestObserver<Integer> to = new TestObserver<Integer>(); + m.subscribe(to); + to.awaitTerminalEvent(); + assertEquals(1, to.errorCount()); + to.assertNoValues(); } @@ -1195,11 +1193,11 @@ public void innerEscapeCompleted() { Observable<Integer> m = source.groupBy(identity, dbl).flatMap(FLATTEN_INTEGER); - TestObserver<Object> ts = new TestObserver<Object>(); - m.subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - System.out.println(ts.values()); + TestObserver<Object> to = new TestObserver<Object>(); + m.subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + System.out.println(to.values()); } /** @@ -1222,8 +1220,7 @@ public void accept(GroupedObservable<Integer, Integer> t1) { inner.get().subscribe(); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o2 = mock(DefaultObserver.class); + Observer<Integer> o2 = TestHelper.mockObserver(); inner.get().subscribe(o2); @@ -1239,16 +1236,16 @@ public void testError2() { Observable<Integer> m = source.groupBy(identity, dbl).flatMap(FLATTEN_INTEGER); - TestObserver<Object> ts = new TestObserver<Object>(); - m.subscribe(ts); - ts.awaitTerminalEvent(); - assertEquals(1, ts.errorCount()); - ts.assertValueCount(1); + TestObserver<Object> to = new TestObserver<Object>(); + m.subscribe(to); + to.awaitTerminalEvent(); + assertEquals(1, to.errorCount()); + to.assertValueCount(1); } @Test public void testgroupByBackpressure() throws InterruptedException { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.range(1, 4000).groupBy(IS_EVEN2).flatMap(new Function<GroupedObservable<Boolean, Integer>, Observable<String>>() { @@ -1297,15 +1294,15 @@ public void accept(Notification<String> t1) { System.out.println("NEXT: " + t1); } - }).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } @Test public void testgroupByBackpressure2() throws InterruptedException { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.range(1, 4000).groupBy(IS_EVEN2).flatMap(new Function<GroupedObservable<Boolean, Integer>, Observable<String>>() { @@ -1329,9 +1326,9 @@ public String apply(Integer l) { }); } - }).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } static Function<GroupedObservable<Integer, Integer>, Observable<Integer>> FLATTEN_INTEGER = new Function<GroupedObservable<Integer, Integer>, Observable<Integer>>() { @@ -1367,22 +1364,22 @@ public void accept(String s) { }); } }); - assertEquals(null, key[0]); + assertNull(key[0]); assertEquals(Arrays.asList("a", "b", "c"), values); } @Test public void testGroupByUnsubscribe() { - final Disposable s = mock(Disposable.class); + final Disposable upstream = mock(Disposable.class); Observable<Integer> o = Observable.unsafeCreate( new ObservableSource<Integer>() { @Override public void subscribe(Observer<? super Integer> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); } } ); - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); o.groupBy(new Function<Integer, Integer>() { @@ -1390,11 +1387,11 @@ public void subscribe(Observer<? super Integer> observer) { public Integer apply(Integer integer) { return null; } - }).subscribe(ts); + }).subscribe(to); - ts.dispose(); + to.dispose(); - verify(s).dispose(); + verify(upstream).dispose(); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java index c23628c4bb..59c5a96332 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java @@ -15,7 +15,7 @@ */ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.*; @@ -30,9 +30,9 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableGroupJoin.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class ObservableGroupJoinTest { @@ -201,7 +201,7 @@ public void onComplete() { } @Override - public void onSubscribe(Disposable s) { + public void onSubscribe(Disposable d) { } } @@ -507,7 +507,7 @@ public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Except @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Object> ps1 = PublishSubject.create(); final PublishSubject<Object> ps2 = PublishSubject.create(); @@ -554,7 +554,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertValueCount(1); @@ -579,7 +579,7 @@ public void run() { @Test public void outerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Object> ps1 = PublishSubject.create(); final PublishSubject<Object> ps2 = PublishSubject.create(); @@ -627,7 +627,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertNoValues(); @@ -689,4 +689,39 @@ public Observable<Object> apply(Object r, Observable<Object> l) throws Exception to.assertResult(2); } + + @Test + public void leftRightState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightObserver o = new LeftRightObserver(js, false); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + o.dispose(); + + assertTrue(o.isDisposed()); + + verify(js).innerValue(false, 1); + verify(js).innerValue(false, 2); + } + + @Test + public void leftRightEndState() { + JoinSupport js = mock(JoinSupport.class); + + LeftRightEndObserver o = new LeftRightEndObserver(js, false, 0); + + assertFalse(o.isDisposed()); + + o.onNext(1); + o.onNext(2); + + assertTrue(o.isDisposed()); + + verify(js).innerClose(false, o); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableHideTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableHideTest.java index 14c6f82c17..d4c2bf002f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableHideTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableHideTest.java @@ -20,6 +20,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.subjects.PublishSubject; public class ObservableHideTest { @@ -42,6 +43,7 @@ public void testHiding() { verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void testHidingError() { PublishSubject<Integer> src = PublishSubject.create(); @@ -60,4 +62,20 @@ public void testHidingError() { verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Observable<Object> o) + throws Exception { + return o.hide(); + } + }); + } + + @Test + public void disposed() { + TestHelper.checkDisposed(PublishSubject.create().hide()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsTest.java index 1a2e7c7782..7e75e12a8d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsTest.java @@ -57,26 +57,26 @@ public void accept(Integer t) { @Test public void testCompletedOkObservable() { - TestObserver<Object> ts = new TestObserver<Object>(); - Observable.range(1, 10).ignoreElements().toObservable().subscribe(ts); - ts.assertNoErrors(); - ts.assertNoValues(); - ts.assertTerminated(); + TestObserver<Object> to = new TestObserver<Object>(); + Observable.range(1, 10).ignoreElements().toObservable().subscribe(to); + to.assertNoErrors(); + to.assertNoValues(); + to.assertTerminated(); // FIXME no longer testable // ts.assertUnsubscribed(); } @Test public void testErrorReceivedObservable() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); TestException ex = new TestException("boo"); - Observable.error(ex).ignoreElements().toObservable().subscribe(ts); - ts.assertNoValues(); - ts.assertTerminated(); + Observable.error(ex).ignoreElements().toObservable().subscribe(to); + to.assertNoValues(); + to.assertTerminated(); // FIXME no longer testable // ts.assertUnsubscribed(); - ts.assertError(TestException.class); - ts.assertErrorMessage("boo"); + to.assertError(TestException.class); + to.assertErrorMessage("boo"); } @Test @@ -124,26 +124,26 @@ public void accept(Integer t) { @Test public void testCompletedOk() { - TestObserver<Object> ts = new TestObserver<Object>(); - Observable.range(1, 10).ignoreElements().subscribe(ts); - ts.assertNoErrors(); - ts.assertNoValues(); - ts.assertTerminated(); + TestObserver<Object> to = new TestObserver<Object>(); + Observable.range(1, 10).ignoreElements().subscribe(to); + to.assertNoErrors(); + to.assertNoValues(); + to.assertTerminated(); // FIXME no longer testable // ts.assertUnsubscribed(); } @Test public void testErrorReceived() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); TestException ex = new TestException("boo"); - Observable.error(ex).ignoreElements().subscribe(ts); - ts.assertNoValues(); - ts.assertTerminated(); + Observable.error(ex).ignoreElements().subscribe(to); + to.assertNoValues(); + to.assertTerminated(); // FIXME no longer testable // ts.assertUnsubscribed(); - ts.assertError(TestException.class); - ts.assertErrorMessage("boo"); + to.assertError(TestException.class); + to.assertErrorMessage("boo"); } @Test @@ -164,17 +164,17 @@ public void run() { @Test public void cancel() { - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.ignoreElements().<Integer>toObservable().test(); + TestObserver<Integer> to = ps.ignoreElements().<Integer>toObservable().test(); - assertTrue(pp.hasObservers()); + assertTrue(ps.hasObservers()); - ts.cancel(); + to.cancel(); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); - TestHelper.checkDisposed(pp.ignoreElements().<Integer>toObservable()); + TestHelper.checkDisposed(ps.ignoreElements().<Integer>toObservable()); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableInternalHelperTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableInternalHelperTest.java index 1e18ef043d..e03d651bda 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableInternalHelperTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableInternalHelperTest.java @@ -29,8 +29,6 @@ public void enums() { assertNotNull(ObservableInternalHelper.MapToInt.values()[0]); assertNotNull(ObservableInternalHelper.MapToInt.valueOf("INSTANCE")); - assertNotNull(ObservableInternalHelper.ErrorMapperFilter.values()[0]); - assertNotNull(ObservableInternalHelper.ErrorMapperFilter.valueOf("INSTANCE")); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java index ff8a2e149a..17d370a56d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableIntervalTest.java @@ -18,6 +18,8 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.internal.operators.observable.ObservableInterval.IntervalObserver; +import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.*; public class ObservableIntervalTest { @@ -34,4 +36,17 @@ public void cancel() { .test() .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); } + + @Test + public void cancelledOnRun() { + TestObserver<Long> to = new TestObserver<Long>(); + IntervalObserver is = new IntervalObserver(to); + to.onSubscribe(is); + + is.dispose(); + + is.run(); + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java index 8ced523827..00a733959d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java @@ -15,7 +15,6 @@ */ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java index 6f3ecd39c8..77f02bfe71 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.NoSuchElementException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapNotificationTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapNotificationTest.java index fae7c60afd..748078fd02 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapNotificationTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapNotificationTest.java @@ -28,7 +28,7 @@ public class ObservableMapNotificationTest { @Test public void testJust() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); Observable.just(1) .flatMap( new Function<Integer, Observable<Object>>() { @@ -49,11 +49,11 @@ public Observable<Object> call() { return Observable.never(); } } - ).subscribe(ts); + ).subscribe(to); - ts.assertNoErrors(); - ts.assertNotComplete(); - ts.assertValue(2); + to.assertNoErrors(); + to.assertNotComplete(); + to.assertValue(2); } @Test @@ -89,7 +89,7 @@ public ObservableSource<Integer> apply(Observable<Object> o) throws Exception { @Test public void onErrorCrash() { - TestObserver<Integer> ts = Observable.<Integer>error(new TestException("Outer")) + TestObserver<Integer> to = Observable.<Integer>error(new TestException("Outer")) .flatMap(Functions.justFunction(Observable.just(1)), new Function<Throwable, Observable<Integer>>() { @Override @@ -101,7 +101,7 @@ public Observable<Integer> apply(Throwable t) throws Exception { .test() .assertFailure(CompositeException.class); - TestHelper.assertError(ts, 0, TestException.class, "Outer"); - TestHelper.assertError(ts, 1, TestException.class, "Inner"); + TestHelper.assertError(to, 0, TestException.class, "Outer"); + TestHelper.assertError(to, 1, TestException.class, "Inner"); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java index 662c1ed8bb..4d271a4197 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.observable; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; import java.util.*; @@ -25,7 +25,7 @@ import io.reactivex.Observer; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.UnicastSubject; @@ -349,19 +349,19 @@ public ObservableSource<Object> apply(Observable<Object> o) throws Exception { @Test public void fusedSync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.range(1, 5) .map(Functions.<Integer>identity()) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedAsync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> us = UnicastSubject.create(); @@ -371,19 +371,19 @@ public void fusedAsync() { TestHelper.emit(us, 1, 2, 3, 4, 5); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedReject() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY | QueueFuseable.BOUNDARY); Observable.range(1, 5) .map(Functions.<Integer>identity()) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMaterializeTest.java index dfc7ba2fab..d163293c69 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMaterializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMaterializeTest.java @@ -101,17 +101,17 @@ public void testMultipleSubscribes() throws InterruptedException, ExecutionExcep @Test public void testWithCompletionCausingError() { - TestObserver<Notification<Integer>> ts = new TestObserver<Notification<Integer>>(); + TestObserver<Notification<Integer>> to = new TestObserver<Notification<Integer>>(); final RuntimeException ex = new RuntimeException("boo"); Observable.<Integer>empty().materialize().doOnNext(new Consumer<Object>() { @Override public void accept(Object t) { throw ex; } - }).subscribe(ts); - ts.assertError(ex); - ts.assertNoValues(); - ts.assertTerminated(); + }).subscribe(to); + to.assertError(ex); + to.assertNoValues(); + to.assertTerminated(); } private static class TestLocalObserver extends DefaultObserver<Notification<String>> { diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java index 2ddcd54994..833f9105c5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeDelayErrorTest.java @@ -429,6 +429,7 @@ public void onNext(String args) { } } + @Test @Ignore("Subscribers should not throw") public void testMergeSourceWhichDoesntPropagateExceptionBack() { @@ -487,15 +488,15 @@ public void onComplete() { @Test public void testErrorInParentObservable() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.mergeDelayError( Observable.just(Observable.just(1), Observable.just(2)) .startWith(Observable.<Integer> error(new RuntimeException())) - ).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertTerminated(); - ts.assertValues(1, 2); - assertEquals(1, ts.errorCount()); + ).subscribe(to); + to.awaitTerminalEvent(); + to.assertTerminated(); + to.assertValues(1, 2); + assertEquals(1, to.errorCount()); } @@ -516,12 +517,12 @@ public void subscribe(Observer<? super Observable<String>> op) { Observer<String> stringObserver = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(stringObserver); + TestObserver<String> to = new TestObserver<String>(stringObserver); Observable<String> m = Observable.mergeDelayError(parentObservable); - m.subscribe(ts); + m.subscribe(to); System.out.println("testErrorInParentObservableDelayed | " + i); - ts.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS); - ts.assertTerminated(); + to.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS); + to.assertTerminated(); verify(stringObserver, times(2)).onNext("hello"); verify(stringObserver, times(1)).onError(any(NullPointerException.class)); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java index 202fe7fdc8..ef52095f25 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeMaxConcurrentTest.java @@ -137,6 +137,7 @@ public void testMergeALotOfSourcesOneByOneSynchronously() { } assertEquals(j, n); } + @Test public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { int n = 10000; @@ -156,7 +157,7 @@ public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() { @Test public void testSimple() { for (int i = 1; i < 100; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i); List<Integer> result = new ArrayList<Integer>(i); for (int j = 1; j <= i; j++) { @@ -164,17 +165,18 @@ public void testSimple() { result.add(j); } - Observable.merge(sourceList, i).subscribe(ts); + Observable.merge(sourceList, i).subscribe(to); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertValueSequence(result); + to.assertNoErrors(); + to.assertTerminated(); + to.assertValueSequence(result); } } + @Test public void testSimpleOneLess() { for (int i = 2; i < 100; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i); List<Integer> result = new ArrayList<Integer>(i); for (int j = 1; j <= i; j++) { @@ -182,13 +184,14 @@ public void testSimpleOneLess() { result.add(j); } - Observable.merge(sourceList, i - 1).subscribe(ts); + Observable.merge(sourceList, i - 1).subscribe(to); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertValueSequence(result); + to.assertNoErrors(); + to.assertTerminated(); + to.assertValueSequence(result); } } + @Test//(timeout = 20000) public void testSimpleAsyncLoop() { IoScheduler ios = (IoScheduler)Schedulers.io(); @@ -201,10 +204,11 @@ public void testSimpleAsyncLoop() { } } } + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i); Set<Integer> expected = new HashSet<Integer>(i); for (int j = 1; j <= i; j++) { @@ -212,21 +216,23 @@ public void testSimpleAsync() { expected.add(j); } - Observable.merge(sourceList, i).subscribe(ts); + Observable.merge(sourceList, i).subscribe(to); - ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - ts.assertNoErrors(); - Set<Integer> actual = new HashSet<Integer>(ts.values()); + to.awaitTerminalEvent(1, TimeUnit.SECONDS); + to.assertNoErrors(); + Set<Integer> actual = new HashSet<Integer>(to.values()); assertEquals(expected, actual); } } + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { for (int i = 0; i < 200; i++) { testSimpleOneLessAsync(); } } + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); @@ -234,7 +240,7 @@ public void testSimpleOneLessAsync() { if (System.currentTimeMillis() - t > TimeUnit.SECONDS.toMillis(9)) { break; } - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i); Set<Integer> expected = new HashSet<Integer>(i); for (int j = 1; j <= i; j++) { @@ -242,11 +248,11 @@ public void testSimpleOneLessAsync() { expected.add(j); } - Observable.merge(sourceList, i - 1).subscribe(ts); + Observable.merge(sourceList, i - 1).subscribe(to); - ts.awaitTerminalEvent(1, TimeUnit.SECONDS); - ts.assertNoErrors(); - Set<Integer> actual = new HashSet<Integer>(ts.values()); + to.awaitTerminalEvent(1, TimeUnit.SECONDS); + to.assertNoErrors(); + Set<Integer> actual = new HashSet<Integer>(to.values()); assertEquals(expected, actual); } @@ -260,12 +266,12 @@ public void testTake() throws Exception { sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io())); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.merge(sourceList, 2).take(5).subscribe(ts); + Observable.merge(sourceList, 2).take(5).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - ts.assertValueCount(5); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertValueCount(5); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java index 85907bcf23..8b69ef593b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeTest.java @@ -133,14 +133,14 @@ public void testUnSubscribeObservableOfObservables() throws InterruptedException @Override public void subscribe(final Observer<? super Observable<Long>> observer) { // verbose on purpose so I can track the inside of it - final Disposable s = Disposables.fromRunnable(new Runnable() { + final Disposable upstream = Disposables.fromRunnable(new Runnable() { @Override public void run() { System.out.println("*** unsubscribed"); unsubscribed.set(true); } }); - observer.onSubscribe(s); + observer.onSubscribe(upstream); new Thread(new Runnable() { @@ -189,11 +189,11 @@ public void testMergeArrayWithThreading() { final TestASynchronousObservable o2 = new TestASynchronousObservable(); Observable<String> m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); - TestObserver<String> ts = new TestObserver<String>(stringObserver); - m.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(stringObserver); + m.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + to.awaitTerminalEvent(); + to.assertNoErrors(); verify(stringObserver, never()).onError(any(Throwable.class)); verify(stringObserver, times(2)).onNext("hello"); @@ -317,8 +317,8 @@ public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior final Observable<String> o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); final Observable<String> o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable<String> o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Observable<String> o4 = Observable.unsafeCreate(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails + final Observable<String> o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); // we expect to lose all of these since o2 is done first and fails + final Observable<String> o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); // we expect to lose all of these since o2 is done first and fails Observable<String> m = Observable.merge(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -339,20 +339,20 @@ public void testError2() { @Test @Ignore("Subscribe should not throw") public void testThrownErrorHandling() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable<String> o1 = Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { + public void subscribe(Observer<? super String> observer) { throw new RuntimeException("fail"); } }); - Observable.merge(o1, o1).subscribe(ts); - ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - System.out.println("Error: " + ts.errors()); + Observable.merge(o1, o1).subscribe(to); + to.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + to.assertTerminated(); + System.out.println("Error: " + to.errors()); } private static class TestSynchronousObservable implements ObservableSource<String> { @@ -425,16 +425,16 @@ public void testUnsubscribeAsObservablesComplete() { AtomicBoolean os2 = new AtomicBoolean(false); Observable<Long> o2 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); - TestObserver<Long> ts = new TestObserver<Long>(); - Observable.merge(o1, o2).subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + Observable.merge(o1, o2).subscribe(to); // we haven't incremented time so nothing should be received yet - ts.assertNoValues(); + to.assertNoValues(); scheduler1.advanceTimeBy(3, TimeUnit.SECONDS); scheduler2.advanceTimeBy(2, TimeUnit.SECONDS); - ts.assertValues(0L, 1L, 2L, 0L, 1L); + to.assertValues(0L, 1L, 2L, 0L, 1L); // not unsubscribed yet assertFalse(os1.get()); assertFalse(os2.get()); @@ -442,18 +442,18 @@ public void testUnsubscribeAsObservablesComplete() { // advance to the end at which point it should complete scheduler1.advanceTimeBy(3, TimeUnit.SECONDS); - ts.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L); + to.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L); assertTrue(os1.get()); assertFalse(os2.get()); // both should be completed now scheduler2.advanceTimeBy(3, TimeUnit.SECONDS); - ts.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L, 2L, 3L, 4L); + to.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L, 2L, 3L, 4L); assertTrue(os1.get()); assertTrue(os2.get()); - ts.assertTerminated(); + to.assertTerminated(); } @Test @@ -467,27 +467,27 @@ public void testEarlyUnsubscribe() { AtomicBoolean os2 = new AtomicBoolean(false); Observable<Long> o2 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2); - TestObserver<Long> ts = new TestObserver<Long>(); - Observable.merge(o1, o2).subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + Observable.merge(o1, o2).subscribe(to); // we haven't incremented time so nothing should be received yet - ts.assertNoValues(); + to.assertNoValues(); scheduler1.advanceTimeBy(3, TimeUnit.SECONDS); scheduler2.advanceTimeBy(2, TimeUnit.SECONDS); - ts.assertValues(0L, 1L, 2L, 0L, 1L); + to.assertValues(0L, 1L, 2L, 0L, 1L); // not unsubscribed yet assertFalse(os1.get()); assertFalse(os2.get()); // early unsubscribe - ts.dispose(); + to.dispose(); assertTrue(os1.get()); assertTrue(os2.get()); - ts.assertValues(0L, 1L, 2L, 0L, 1L); + to.assertValues(0L, 1L, 2L, 0L, 1L); // FIXME not happening anymore // ts.assertUnsubscribed(); } @@ -502,12 +502,12 @@ public void subscribe(final Observer<? super Long> child) { .take(5) .subscribe(new Observer<Long>() { @Override - public void onSubscribe(final Disposable s) { + public void onSubscribe(final Disposable d) { child.onSubscribe(Disposables.fromRunnable(new Runnable() { @Override public void run() { unsubscribed.set(true); - s.dispose(); + d.dispose(); } })); } @@ -540,14 +540,14 @@ public void testConcurrency() { for (int i = 0; i < 10; i++) { Observable<Integer> merge = Observable.merge(o, o, o); - TestObserver<Integer> ts = new TestObserver<Integer>(); - merge.subscribe(ts); - - ts.awaitTerminalEvent(3, TimeUnit.SECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertComplete(); - List<Integer> onNextEvents = ts.values(); + TestObserver<Integer> to = new TestObserver<Integer>(); + merge.subscribe(to); + + to.awaitTerminalEvent(3, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + to.assertComplete(); + List<Integer> onNextEvents = to.values(); assertEquals(30000, onNextEvents.size()); // System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size()); } @@ -559,13 +559,13 @@ public void testConcurrencyWithSleeping() { Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(final Observer<? super Integer> s) { + public void subscribe(final Observer<? super Integer> observer) { Worker inner = Schedulers.newThread().createWorker(); final CompositeDisposable as = new CompositeDisposable(); as.add(Disposables.empty()); as.add(inner); - s.onSubscribe(as); + observer.onSubscribe(as); inner.schedule(new Runnable() { @@ -573,7 +573,7 @@ public void subscribe(final Observer<? super Integer> s) { public void run() { try { for (int i = 0; i < 100; i++) { - s.onNext(1); + observer.onNext(1); try { Thread.sleep(1); } catch (InterruptedException e) { @@ -581,10 +581,10 @@ public void run() { } } } catch (Exception e) { - s.onError(e); + observer.onError(e); } as.dispose(); - s.onComplete(); + observer.onComplete(); } }); @@ -593,12 +593,12 @@ public void run() { for (int i = 0; i < 10; i++) { Observable<Integer> merge = Observable.merge(o, o, o); - TestObserver<Integer> ts = new TestObserver<Integer>(); - merge.subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + merge.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertComplete(); - List<Integer> onNextEvents = ts.values(); + to.awaitTerminalEvent(); + to.assertComplete(); + List<Integer> onNextEvents = to.values(); assertEquals(300, onNextEvents.size()); // System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size()); } @@ -609,13 +609,13 @@ public void testConcurrencyWithBrokenOnCompleteContract() { Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(final Observer<? super Integer> s) { + public void subscribe(final Observer<? super Integer> observer) { Worker inner = Schedulers.newThread().createWorker(); final CompositeDisposable as = new CompositeDisposable(); as.add(Disposables.empty()); as.add(inner); - s.onSubscribe(as); + observer.onSubscribe(as); inner.schedule(new Runnable() { @@ -623,15 +623,15 @@ public void subscribe(final Observer<? super Integer> s) { public void run() { try { for (int i = 0; i < 10000; i++) { - s.onNext(i); + observer.onNext(i); } } catch (Exception e) { - s.onError(e); + observer.onError(e); } as.dispose(); - s.onComplete(); - s.onComplete(); - s.onComplete(); + observer.onComplete(); + observer.onComplete(); + observer.onComplete(); } }); @@ -640,13 +640,13 @@ public void run() { for (int i = 0; i < 10; i++) { Observable<Integer> merge = Observable.merge(o, o, o); - TestObserver<Integer> ts = new TestObserver<Integer>(); - merge.subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + merge.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - ts.assertComplete(); - List<Integer> onNextEvents = ts.values(); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertComplete(); + List<Integer> onNextEvents = to.values(); assertEquals(30000, onNextEvents.size()); // System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size()); } @@ -825,95 +825,95 @@ public void onNext(Integer t) { @Ignore("Null values not permitted") public void mergeWithNullValues() { System.out.println("mergeWithNullValues"); - TestObserver<String> ts = new TestObserver<String>(); - Observable.merge(Observable.just(null, "one"), Observable.just("two", null)).subscribe(ts); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValues(null, "one", "two", null); + TestObserver<String> to = new TestObserver<String>(); + Observable.merge(Observable.just(null, "one"), Observable.just("two", null)).subscribe(to); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValues(null, "one", "two", null); } @Test @Ignore("Null values are no longer permitted") public void mergeWithTerminalEventAfterUnsubscribe() { System.out.println("mergeWithTerminalEventAfterUnsubscribe"); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable<String> bad = Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { - s.onNext("two"); + public void subscribe(Observer<? super String> observer) { + observer.onNext("two"); // FIXME can't cancel downstream // s.unsubscribe(); // s.onComplete(); } }); - Observable.merge(Observable.just(null, "one"), bad).subscribe(ts); - ts.assertNoErrors(); - ts.assertValues(null, "one", "two"); + Observable.merge(Observable.just(null, "one"), bad).subscribe(to); + to.assertNoErrors(); + to.assertValues(null, "one", "two"); } @Test @Ignore("Null values are not permitted") public void mergingNullObservable() { - TestObserver<String> ts = new TestObserver<String>(); - Observable.merge(Observable.just("one"), null).subscribe(ts); - ts.assertNoErrors(); - ts.assertValue("one"); + TestObserver<String> to = new TestObserver<String>(); + Observable.merge(Observable.just("one"), null).subscribe(to); + to.assertNoErrors(); + to.assertValue("one"); } @Test public void merge1AsyncStreamOf1() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(1, 1).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(1, 1).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1, to.values().size()); } @Test public void merge1AsyncStreamOf1000() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(1, 1000).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(1, 1000).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1000, to.values().size()); } @Test public void merge10AsyncStreamOf1000() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(10, 1000).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(10000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(10, 1000).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(10000, to.values().size()); } @Test public void merge1000AsyncStreamOf1000() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(1000, 1000).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1000000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(1000, 1000).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1000000, to.values().size()); } @Test public void merge2000AsyncStreamOf100() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(2000, 100).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(200000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(2000, 100).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(200000, to.values().size()); } @Test public void merge100AsyncStreamOf1() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNAsyncStreamsOfN(100, 1).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(100, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNAsyncStreamsOfN(100, 1).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(100, to.values().size()); } private Observable<Integer> mergeNAsyncStreamsOfN(final int outerSize, final int innerSize) { @@ -931,47 +931,47 @@ public Observable<Integer> apply(Integer i) { @Test public void merge1SyncStreamOf1() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNSyncStreamsOfN(1, 1).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNSyncStreamsOfN(1, 1).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1, to.values().size()); } @Test public void merge1SyncStreamOf1000000() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNSyncStreamsOfN(1, 1000000).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1000000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNSyncStreamsOfN(1, 1000000).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1000000, to.values().size()); } @Test public void merge1000SyncStreamOf1000() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNSyncStreamsOfN(1000, 1000).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1000000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNSyncStreamsOfN(1000, 1000).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1000000, to.values().size()); } @Test public void merge10000SyncStreamOf10() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNSyncStreamsOfN(10000, 10).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(100000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNSyncStreamsOfN(10000, 10).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(100000, to.values().size()); } @Test public void merge1000000SyncStreamOf1() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - mergeNSyncStreamsOfN(1000000, 1).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(1000000, ts.values().size()); + TestObserver<Integer> to = new TestObserver<Integer>(); + mergeNSyncStreamsOfN(1000000, 1).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(1000000, to.values().size()); } private Observable<Integer> mergeNSyncStreamsOfN(final int outerSize, final int innerSize) { @@ -1014,7 +1014,7 @@ public boolean hasNext() { @Test public void mergeManyAsyncSingle() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable<Observable<Integer>> os = Observable.range(1, 10000) .map(new Function<Integer, Observable<Integer>>() { @@ -1023,8 +1023,8 @@ public Observable<Integer> apply(final Integer i) { return Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); if (i < 500) { try { Thread.sleep(1); @@ -1032,18 +1032,18 @@ public void subscribe(Observer<? super Integer> s) { e.printStackTrace(); } } - s.onNext(i); - s.onComplete(); + observer.onNext(i); + observer.onComplete(); } }).subscribeOn(Schedulers.computation()).cache(); } }); - Observable.merge(os).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(10000, ts.values().size()); + Observable.merge(os).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(10000, to.values().size()); } Function<Integer, Observable<Integer>> toScalar = new Function<Integer, Observable<Integer>>() { @@ -1061,35 +1061,37 @@ public Observable<Integer> apply(Integer t) { }; ; - void runMerge(Function<Integer, Observable<Integer>> func, TestObserver<Integer> ts) { + void runMerge(Function<Integer, Observable<Integer>> func, TestObserver<Integer> to) { List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 1000; i++) { list.add(i); } Observable<Integer> source = Observable.fromIterable(list); - source.flatMap(func).subscribe(ts); + source.flatMap(func).subscribe(to); - if (ts.values().size() != 1000) { - System.out.println(ts.values()); + if (to.values().size() != 1000) { + System.out.println(to.values()); } - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValueSequence(list); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValueSequence(list); } @Test public void testFastMergeFullScalar() { runMerge(toScalar, new TestObserver<Integer>()); } + @Test public void testFastMergeHiddenScalar() { runMerge(toHiddenScalar, new TestObserver<Integer>()); } + @Test public void testSlowMergeFullScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { int remaining = req; @Override @@ -1100,13 +1102,14 @@ public void onNext(Integer t) { } } }; - runMerge(toScalar, ts); + runMerge(toScalar, to); } } + @Test public void testSlowMergeHiddenScalar() { for (final int req : new int[] { 16, 32, 64, 128, 256 }) { - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { int remaining = req; @Override public void onNext(Integer t) { @@ -1116,7 +1119,7 @@ public void onNext(Integer t) { } } }; - runMerge(toHiddenScalar, ts); + runMerge(toHiddenScalar, to); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java new file mode 100644 index 0000000000..d9a54c916a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java @@ -0,0 +1,174 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import io.reactivex.subjects.*; + +public class ObservableMergeWithCompletableTest { + + @Test + public void normal() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5).mergeWith( + Completable.fromAction(new Action() { + @Override + public void run() throws Exception { + to.onNext(100); + } + }) + ) + .subscribe(to); + + to.assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void take() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.range(1, 5).mergeWith( + Completable.complete() + ) + .take(3) + .subscribe(to); + + to.assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + to.cancel(); + + assertFalse(ps.hasObservers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .mergeWith(Completable.complete()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Observable.never() + .mergeWith(Completable.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 1000; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + final CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertResult(1); + } + } + + @Test + public void isDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + } + }.mergeWith(Completable.complete()) + .take(1) + .test() + .assertResult(1); + } + + @Test + public void cancelOtherOnMainError() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", cs.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject<Integer> ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", cs.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java new file mode 100644 index 0000000000..ee9eb9b576 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java @@ -0,0 +1,310 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableMergeWithMaybeTest { + + @Test + public void normal() { + Observable.range(1, 5) + .mergeWith(Maybe.just(100)) + .test() + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void emptyOther() { + Observable.range(1, 5) + .mergeWith(Maybe.<Integer>empty()) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalLong() { + Observable.range(1, 512) + .mergeWith(Maybe.just(100)) + .test() + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void take() { + Observable.range(1, 5) + .mergeWith(Maybe.just(100)) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + to.cancel(); + + assertFalse(ps.hasObservers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .mergeWith(Maybe.just(100)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Observable.never() + .mergeWith(Maybe.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 10000; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + TestHelper.race(r1, r2); + + to.assertResult(1, 1); + } + } + + @Test + public void onNextSlowPath() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + } + } + }); + + ps.onNext(1); + cs.onSuccess(3); + + ps.onNext(4); + ps.onComplete(); + + to.assertResult(1, 2, 3, 4); + } + + @Test + public void onSuccessSlowPath() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + ps.onNext(1); + + ps.onNext(3); + ps.onComplete(); + + to.assertResult(1, 2, 3); + } + + @Test + public void onErrorMainOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<?>> observerRef = new AtomicReference<Observer<?>>(); + TestObserver<Integer> to = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observerRef.set(observer); + } + } + .mergeWith(Maybe.<Integer>error(new IOException())) + .test(); + + observerRef.get().onError(new TestException()); + + to.assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onErrorOtherOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable.error(new IOException()) + .mergeWith(Maybe.error(new TestException())) + .test() + .assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnSubscribeMain() { + TestHelper.checkDoubleOnSubscribeObservable( + new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f.mergeWith(Maybe.just(1)); + } + } + ); + } + + @Test + public void isDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + } + }.mergeWith(Maybe.<Integer>empty()) + .take(1) + .test() + .assertResult(1); + } + + @Test + public void onNextSlowPathCreateQueue() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final MaybeSubject<Integer> cs = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + ps.onNext(3); + } + } + }); + + cs.onSuccess(0); + ps.onNext(1); + + ps.onNext(4); + ps.onComplete(); + + to.assertResult(0, 1, 2, 3, 4); + } + + @Test + public void cancelOtherOnMainError() { + PublishSubject<Integer> ps = PublishSubject.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(ms).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ms.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject<Integer> ps = PublishSubject.create(); + MaybeSubject<Integer> ms = MaybeSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(ms).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + ms.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ms.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java new file mode 100644 index 0000000000..0d8fb3432b --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java @@ -0,0 +1,302 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.*; + +public class ObservableMergeWithSingleTest { + + @Test + public void normal() { + Observable.range(1, 5) + .mergeWith(Single.just(100)) + .test() + .assertResult(1, 2, 3, 4, 5, 100); + } + + @Test + public void normalLong() { + Observable.range(1, 512) + .mergeWith(Single.just(100)) + .test() + .assertValueCount(513) + .assertComplete(); + } + + @Test + public void take() { + Observable.range(1, 5) + .mergeWith(Single.just(100)) + .take(3) + .test() + .assertResult(1, 2, 3); + } + + @Test + public void cancel() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + to.cancel(); + + assertFalse(ps.hasObservers()); + assertFalse(cs.hasObservers()); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .mergeWith(Single.just(100)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void otherError() { + Observable.never() + .mergeWith(Single.error(new TestException())) + .test() + .assertFailure(TestException.class); + } + + @Test + public void completeRace() { + for (int i = 0; i < 10000; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cs.onSuccess(1); + } + }; + + TestHelper.race(r1, r2); + + to.assertResult(1, 1); + } + } + + @Test + public void onNextSlowPath() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + } + } + }); + + ps.onNext(1); + cs.onSuccess(3); + + ps.onNext(4); + ps.onComplete(); + + to.assertResult(1, 2, 3, 4); + } + + @Test + public void onSuccessSlowPath() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + cs.onSuccess(2); + } + } + }); + + ps.onNext(1); + + ps.onNext(3); + ps.onComplete(); + + to.assertResult(1, 2, 3); + } + + @Test + public void onErrorMainOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<?>> observerRef = new AtomicReference<Observer<?>>(); + TestObserver<Integer> to = new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observerRef.set(observer); + } + } + .mergeWith(Single.<Integer>error(new IOException())) + .test(); + + observerRef.get().onError(new TestException()); + + to.assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onErrorOtherOverflow() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable.error(new IOException()) + .mergeWith(Single.error(new TestException())) + .test() + .assertFailure(IOException.class) + ; + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnSubscribeMain() { + TestHelper.checkDoubleOnSubscribeObservable( + new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) + throws Exception { + return f.mergeWith(Single.just(1)); + } + } + ); + } + + @Test + public void isDisposed() { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + + assertFalse(((Disposable)observer).isDisposed()); + + observer.onNext(1); + + assertTrue(((Disposable)observer).isDisposed()); + } + }.mergeWith(Single.<Integer>just(1)) + .take(1) + .test() + .assertResult(1); + } + + @Test + public void onNextSlowPathCreateQueue() { + final PublishSubject<Integer> ps = PublishSubject.create(); + final SingleSubject<Integer> cs = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(cs).subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + ps.onNext(3); + } + } + }); + + cs.onSuccess(0); + ps.onNext(1); + + ps.onNext(4); + ps.onComplete(); + + to.assertResult(0, 1, 2, 3, 4); + } + + @Test + public void cancelOtherOnMainError() { + PublishSubject<Integer> ps = PublishSubject.create(); + SingleSubject<Integer> ss = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(ss).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ss.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ss.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject<Integer> ps = PublishSubject.create(); + SingleSubject<Integer> ss = SingleSubject.create(); + + TestObserver<Integer> to = ps.mergeWith(ss).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ss.hasObservers()); + + ss.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ss.hasObservers()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java index 21460f2b21..48cbe91908 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java @@ -21,17 +21,18 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import io.reactivex.annotations.Nullable; import org.junit.Test; import org.mockito.InOrder; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; +import io.reactivex.annotations.Nullable; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.operators.flowable.FlowableObserveOnTest.DisposeTrackingScheduler; import io.reactivex.internal.operators.observable.ObservableObserveOn.ObserveOnObserver; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.observers.*; @@ -64,13 +65,13 @@ public void testOrdering() throws InterruptedException { Observer<String> observer = TestHelper.mockObserver(); InOrder inOrder = inOrder(observer); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); - obs.observeOn(Schedulers.computation()).subscribe(ts); + obs.observeOn(Schedulers.computation()).subscribe(to); - ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); - if (ts.errors().size() > 0) { - for (Throwable t : ts.errors()) { + to.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + if (to.errors().size() > 0) { + for (Throwable t : to.errors()) { t.printStackTrace(); } fail("failed with exception"); @@ -145,10 +146,8 @@ public void observeOnTheSameSchedulerTwice() { Observable<Integer> o = Observable.just(1, 2, 3); Observable<Integer> o2 = o.observeOn(scheduler); - @SuppressWarnings("unchecked") - DefaultObserver<Object> observer1 = mock(DefaultObserver.class); - @SuppressWarnings("unchecked") - DefaultObserver<Object> observer2 = mock(DefaultObserver.class); + Observer<Object> observer1 = TestHelper.mockObserver(); + Observer<Object> observer2 = TestHelper.mockObserver(); InOrder inOrder1 = inOrder(observer1); InOrder inOrder2 = inOrder(observer2); @@ -368,8 +367,7 @@ public void testDelayedErrorDeliveryWhenSafeSubscriberUnsubscribes() { Observable<Integer> source = Observable.concat(Observable.<Integer> error(new TestException()), Observable.just(1)); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o = mock(DefaultObserver.class); + Observer<Integer> o = TestHelper.mockObserver(); InOrder inOrder = inOrder(o); source.observeOn(testScheduler).subscribe(o); @@ -388,13 +386,13 @@ public void testAfterUnsubscribeCalledThenObserverOnNextNeverCalled() { final TestScheduler testScheduler = new TestScheduler(); final Observer<Integer> observer = TestHelper.mockObserver(); - TestObserver<Integer> ts = new TestObserver<Integer>(observer); + TestObserver<Integer> to = new TestObserver<Integer>(observer); Observable.just(1, 2, 3) .observeOn(testScheduler) - .subscribe(ts); + .subscribe(to); - ts.dispose(); + to.dispose(); testScheduler.advanceTimeBy(1, TimeUnit.SECONDS); final InOrder inOrder = inOrder(observer); @@ -429,23 +427,23 @@ public boolean hasNext() { } }); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); o .take(7) .observeOn(Schedulers.newThread()) - .subscribe(ts); + .subscribe(to); - ts.awaitTerminalEvent(); - ts.assertValues(0, 1, 2, 3, 4, 5, 6); + to.awaitTerminalEvent(); + to.assertValues(0, 1, 2, 3, 4, 5, 6); assertEquals(7, generated.get()); } @Test public void testAsyncChild() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.range(0, 100000).observeOn(Schedulers.newThread()).observeOn(Schedulers.newThread()).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.range(0, 100000).observeOn(Schedulers.newThread()).observeOn(Schedulers.newThread()).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } @Test @@ -566,33 +564,33 @@ public void inputAsyncFusedErrorDelayed() { @Test public void outputFused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.range(1, 5).hide() .observeOn(Schedulers.single()) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5); } @Test public void outputFusedReject() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Observable.range(1, 5).hide() .observeOn(Schedulers.single()) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5); } @Test public void inputOutputAsyncFusedError() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> us = UnicastSubject.create(); @@ -605,14 +603,14 @@ public void inputOutputAsyncFusedError() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @Test public void inputOutputAsyncFusedErrorDelayed() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); UnicastSubject<Integer> us = UnicastSubject.create(); @@ -625,7 +623,7 @@ public void inputOutputAsyncFusedErrorDelayed() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); - ObserverFusion.assertFusion(to, QueueDisposable.ASYNC) + ObserverFusion.assertFusion(to, QueueFuseable.ASYNC) .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } @@ -638,19 +636,19 @@ public void outputFusedCancelReentrant() throws Exception { us.observeOn(Schedulers.single()) .subscribe(new Observer<Integer>() { - Disposable d; + Disposable upstream; int count; @Override public void onSubscribe(Disposable d) { - this.d = d; - ((QueueDisposable<?>)d).requestFusion(QueueDisposable.ANY); + this.upstream = d; + ((QueueDisposable<?>)d).requestFusion(QueueFuseable.ANY); } @Override public void onNext(Integer value) { if (++count == 1) { us.onNext(2); - d.dispose(); + upstream.dispose(); cdl.countDown(); } } @@ -719,4 +717,133 @@ public void clear() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } + + @Test + public void outputFusedOneSignal() { + final BehaviorSubject<Integer> bs = BehaviorSubject.createDefault(1); + + bs.observeOn(ImmediateThinScheduler.INSTANCE) + .concatMap(new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer v) + throws Exception { + return Observable.just(v + 1); + } + }) + .subscribeWith(new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 2) { + bs.onNext(2); + } + } + }) + .assertValuesOnly(2, 3); + } + + @Test + public void workerNotDisposedPrematurelyNormalInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Observable.concat( + Observable.just(1).hide().observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelySyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + Observable.concat( + Observable.just(1).observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void workerNotDisposedPrematurelyAsyncInNormalOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + UnicastSubject<Integer> up = UnicastSubject.create(); + up.onNext(1); + up.onComplete(); + + Observable.concat( + up.observeOn(s), + Observable.just(2) + ) + .test() + .assertResult(1, 2); + + assertEquals(1, s.disposedCount.get()); + } + + static final class TestObserverFusedCanceling + extends TestObserver<Integer> { + + TestObserverFusedCanceling() { + super(); + initialFusionMode = QueueFuseable.ANY; + } + + @Override + public void onComplete() { + cancel(); + super.onComplete(); + } + } + + @Test + public void workerNotDisposedPrematurelyNormalInAsyncOut() { + DisposeTrackingScheduler s = new DisposeTrackingScheduler(); + + TestObserver<Integer> to = new TestObserverFusedCanceling(); + + Observable.just(1).hide().observeOn(s).subscribe(to); + + assertEquals(1, s.disposedCount.get()); + } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final UnicastSubject<Integer> us = UnicastSubject.create(); + + TestObserver<Integer> to = us.hide() + .observeOn(Schedulers.io()) + .observeOn(Schedulers.single()) + .unsubscribeOn(Schedulers.computation()) + .firstOrError() + .test(); + + for (int i = 0; us.hasObservers() && i < 10000; i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java index 0a94717712..fa48b217f8 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -126,8 +125,7 @@ public Observable<String> apply(Throwable t1) { }; Observable<String> o = Observable.unsafeCreate(w).onErrorResumeNext(resume); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); + Observer<String> observer = TestHelper.mockObserver(); o.subscribe(observer); try { @@ -151,7 +149,7 @@ public Observable<String> apply(Throwable t1) { @Test @Ignore("Failed operator may leave the child Observer in an inconsistent state which prevents further error delivery.") public void testOnErrorResumeReceivesErrorFromPreviousNonProtectedOperator() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.just(1).lift(new ObservableOperator<String, Integer>() { @Override @@ -170,11 +168,11 @@ public Observable<String> apply(Throwable t1) { } } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - System.out.println(ts.values()); - ts.assertValue("success"); + to.assertTerminated(); + System.out.println(to.values()); + to.assertValue("success"); } /** @@ -184,7 +182,7 @@ public Observable<String> apply(Throwable t1) { @Test @Ignore("A crashing operator may leave the downstream in an inconsistent state and not suitable for event delivery") public void testOnErrorResumeReceivesErrorFromPreviousNonProtectedOperatorOnNext() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.just(1).lift(new ObservableOperator<String, Integer>() { @Override @@ -192,8 +190,8 @@ public Observer<? super Integer> apply(final Observer<? super String> t1) { return new Observer<Integer>() { @Override - public void onSubscribe(Disposable s) { - t1.onSubscribe(s); + public void onSubscribe(Disposable d) { + t1.onSubscribe(d); } @Override @@ -225,11 +223,11 @@ public Observable<String> apply(Throwable t1) { } } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - System.out.println(ts.values()); - ts.assertValue("success"); + to.assertTerminated(); + System.out.println(to.values()); + to.assertValue("success"); } @Test @@ -259,12 +257,11 @@ public Observable<String> apply(Throwable t1) { }); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); + Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - o.subscribe(ts); - ts.awaitTerminalEvent(); + TestObserver<String> to = new TestObserver<String>(observer); + o.subscribe(to); + to.awaitTerminalEvent(); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); @@ -314,7 +311,7 @@ public void run() { @Test public void testBackpressure() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 100000) .onErrorResumeNext(new Function<Throwable, Observable<Integer>>() { @@ -342,9 +339,9 @@ public Integer apply(Integer t1) { } }) - .subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + .subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaObservableTest.java index b4c490524c..3dab17f83f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaObservableTest.java @@ -29,9 +29,9 @@ public class ObservableOnErrorResumeNextViaObservableTest { @Test public void testResumeNext() { - Disposable s = mock(Disposable.class); + Disposable upstream = mock(Disposable.class); // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); + TestObservable f = new TestObservable(upstream, "one", "fail", "two", "three"); Observable<String> w = Observable.unsafeCreate(f); Observable<String> resume = Observable.just("twoResume", "threeResume"); Observable<String> observable = w.onErrorResumeNext(resume); @@ -133,12 +133,11 @@ public void subscribe(Observer<? super String> t1) { Observable<String> resume = Observable.just("resume"); Observable<String> observable = testObservable.subscribeOn(Schedulers.io()).onErrorResumeNext(resume); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); - TestObserver<String> ts = new TestObserver<String>(observer); - observable.subscribe(ts); + Observer<String> observer = TestHelper.mockObserver(); + TestObserver<String> to = new TestObserver<String>(observer); + observable.subscribe(to); - ts.awaitTerminalEvent(); + to.awaitTerminalEvent(); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); @@ -147,19 +146,19 @@ public void subscribe(Observer<? super String> t1) { static class TestObservable implements ObservableSource<String> { - final Disposable s; + final Disposable upstream; final String[] values; Thread t; - TestObservable(Disposable s, String... values) { - this.s = s; + TestObservable(Disposable upstream, String... values) { + this.upstream = upstream; this.values = values; } @Override public void subscribe(final Observer<? super String> observer) { System.out.println("TestObservable subscribed to ..."); - observer.onSubscribe(s); + observer.onSubscribe(upstream); t = new Thread(new Runnable() { @Override @@ -190,7 +189,7 @@ public void run() { @Test public void testBackpressure() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 100000) .onErrorResumeNext(Observable.just(1)) .observeOn(Schedulers.computation()) @@ -211,8 +210,8 @@ public Integer apply(Integer t1) { } }) - .subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + .subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturnTest.java index d03b184442..142d4a7f9f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturnTest.java @@ -46,8 +46,7 @@ public String apply(Throwable e) { }); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); + Observer<String> observer = TestHelper.mockObserver(); observable.subscribe(observer); try { @@ -82,8 +81,7 @@ public String apply(Throwable e) { }); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); + Observer<String> observer = TestHelper.mockObserver(); observable.subscribe(observer); try { @@ -128,11 +126,10 @@ public String apply(Throwable t1) { }); - @SuppressWarnings("unchecked") - DefaultObserver<String> observer = mock(DefaultObserver.class); - TestObserver<String> ts = new TestObserver<String>(observer); - observable.subscribe(ts); - ts.awaitTerminalEvent(); + Observer<String> observer = TestHelper.mockObserver(); + TestObserver<String> to = new TestObserver<String>(observer); + observable.subscribe(to); + to.awaitTerminalEvent(); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); @@ -144,7 +141,7 @@ public String apply(Throwable t1) { @Test public void testBackpressure() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 100000) .onErrorReturn(new Function<Throwable, Integer>() { @@ -172,9 +169,9 @@ public Integer apply(Integer t1) { } }) - .subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + .subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } private static class TestObservable implements ObservableSource<String> { diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java index adab10b79d..50312e3884 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnExceptionResumeNextViaObservableTest.java @@ -183,10 +183,9 @@ public String apply(String s) { verify(observer, times(1)).onComplete(); } - @Test public void testBackpressure() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(0, 100000) .onExceptionResumeNext(Observable.just(1)) .observeOn(Schedulers.computation()) @@ -207,12 +206,11 @@ public Integer apply(Integer t1) { } }) - .subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + .subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); } - private static class TestObservable implements ObservableSource<String> { final String[] values; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java new file mode 100644 index 0000000000..b268e5b2ed --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java @@ -0,0 +1,794 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; +import io.reactivex.subjects.PublishSubject; + +public class ObservablePublishAltTest { + + @Test + public void testPublish() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + ConnectableObservable<String> o = Observable.unsafeCreate(new ObservableSource<String>() { + + @Override + public void subscribe(final Observer<? super String> observer) { + observer.onSubscribe(Disposables.empty()); + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + observer.onNext("one"); + observer.onComplete(); + } + }).start(); + } + }).publish(); + + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + o.subscribe(new Consumer<String>() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + o.subscribe(new Consumer<String>() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + Disposable connection = o.connect(); + try { + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } finally { + connection.dispose(); + } + } + + @Test + public void testBackpressureFastSlow() { + ConnectableObservable<Integer> is = Observable.range(1, Flowable.bufferSize() * 2).publish(); + Observable<Integer> fast = is.observeOn(Schedulers.computation()) + .doOnComplete(new Action() { + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed FAST"); + } + }); + + Observable<Integer> slow = is.observeOn(Schedulers.computation()).map(new Function<Integer, Integer>() { + int c; + + @Override + public Integer apply(Integer i) { + if (c == 0) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + c++; + return i; + } + + }).doOnComplete(new Action() { + + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed SLOW"); + } + + }); + + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.merge(fast, slow).subscribe(to); + is.connect(); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, to.valueCount()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStreamUsingSelector() { + final AtomicInteger emitted = new AtomicInteger(); + Observable<Integer> xs = Observable.range(0, Flowable.bufferSize() * 2).doOnNext(new Consumer<Integer>() { + + @Override + public void accept(Integer t1) { + emitted.incrementAndGet(); + } + + }); + TestObserver<Integer> to = new TestObserver<Integer>(); + xs.publish(new Function<Observable<Integer>, Observable<Integer>>() { + + @Override + public Observable<Integer> apply(Observable<Integer> xs) { + return xs.takeUntil(xs.skipWhile(new Predicate<Integer>() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })); + } + + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertValues(0, 1, 2, 3); + assertEquals(5, emitted.get()); + System.out.println(to.values()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStream() { + Observable<Integer> xs = Observable.range(0, Flowable.bufferSize() * 2); + TestObserver<Integer> to = new TestObserver<Integer>(); + ConnectableObservable<Integer> xsp = xs.publish(); + xsp.takeUntil(xsp.skipWhile(new Predicate<Integer>() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })).subscribe(to); + xsp.connect(); + System.out.println(to.values()); + } + + @Test(timeout = 10000) + public void testBackpressureTwoConsumers() { + final AtomicInteger sourceEmission = new AtomicInteger(); + final AtomicBoolean sourceUnsubscribed = new AtomicBoolean(); + final Observable<Integer> source = Observable.range(1, 100) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer t1) { + sourceEmission.incrementAndGet(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + sourceUnsubscribed.set(true); + } + }).share(); + ; + + final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); + final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); + + final TestObserver<Integer> to2 = new TestObserver<Integer>(); + + final TestObserver<Integer> to1 = new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + if (valueCount() == 2) { + source.doOnDispose(new Action() { + @Override + public void run() { + child2Unsubscribed.set(true); + } + }).take(5).subscribe(to2); + } + super.onNext(t); + } + }; + + source.doOnDispose(new Action() { + @Override + public void run() { + child1Unsubscribed.set(true); + } + }).take(5) + .subscribe(to1); + + to1.awaitTerminalEvent(); + to2.awaitTerminalEvent(); + + to1.assertNoErrors(); + to2.assertNoErrors(); + + assertTrue(sourceUnsubscribed.get()); + assertTrue(child1Unsubscribed.get()); + assertTrue(child2Unsubscribed.get()); + + to1.assertValues(1, 2, 3, 4, 5); + to2.assertValues(4, 5, 6, 7, 8); + + assertEquals(8, sourceEmission.get()); + } + + @Test + public void testConnectWithNoSubscriber() { + TestScheduler scheduler = new TestScheduler(); + ConnectableObservable<Long> co = Observable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); + co.connect(); + // Emit 0 + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + TestObserver<Long> to = new TestObserver<Long>(); + co.subscribe(to); + // Emit 1 and 2 + scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS); + to.assertValues(1L, 2L); + to.assertNoErrors(); + to.assertTerminated(); + } + + @Test + public void testSubscribeAfterDisconnectThenConnect() { + ConnectableObservable<Integer> source = Observable.just(1).publish(); + + TestObserver<Integer> to1 = new TestObserver<Integer>(); + + source.subscribe(to1); + + Disposable connection = source.connect(); + + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); + + TestObserver<Integer> to2 = new TestObserver<Integer>(); + + source.subscribe(to2); + + Disposable connection2 = source.connect(); + + to2.assertValue(1); + to2.assertNoErrors(); + to2.assertTerminated(); + + System.out.println(connection); + System.out.println(connection2); + } + + @Test + public void testNoSubscriberRetentionOnCompleted() { + ObservablePublish<Integer> source = (ObservablePublish<Integer>)Observable.just(1).publish(); + + TestObserver<Integer> to1 = new TestObserver<Integer>(); + + source.subscribe(to1); + + to1.assertNoValues(); + to1.assertNoErrors(); + to1.assertNotComplete(); + + source.connect(); + + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); + + assertNull(source.current.get()); + } + + @Test + public void testNonNullConnection() { + ConnectableObservable<Object> source = Observable.never().publish(); + + assertNotNull(source.connect()); + assertNotNull(source.connect()); + } + + @Test + public void testNoDisconnectSomeoneElse() { + ConnectableObservable<Object> source = Observable.never().publish(); + + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); + + connection1.dispose(); + + Disposable connection3 = source.connect(); + + connection2.dispose(); + + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); + } + + @SuppressWarnings("unchecked") + static boolean checkPublishDisposed(Disposable d) { + return ((ObservablePublish.PublishObserver<Object>)d).isDisposed(); + } + + @Test + public void testConnectIsIdempotent() { + final AtomicInteger calls = new AtomicInteger(); + Observable<Integer> source = Observable.unsafeCreate(new ObservableSource<Integer>() { + @Override + public void subscribe(Observer<? super Integer> t) { + t.onSubscribe(Disposables.empty()); + calls.getAndIncrement(); + } + }); + + ConnectableObservable<Integer> conn = source.publish(); + + assertEquals(0, calls.get()); + + conn.connect(); + conn.connect(); + + assertEquals(1, calls.get()); + + conn.connect().dispose(); + + conn.connect(); + conn.connect(); + + assertEquals(2, calls.get()); + } + + @Test + public void testObserveOn() { + ConnectableObservable<Integer> co = Observable.range(0, 1000).publish(); + Observable<Integer> obs = co.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List<TestObserver<Integer>> tos = new ArrayList<TestObserver<Integer>>(); + for (int k = 1; k < j; k++) { + TestObserver<Integer> to = new TestObserver<Integer>(); + tos.add(to); + obs.subscribe(to); + } + + Disposable connection = co.connect(); + + for (TestObserver<Integer> to : tos) { + to.awaitTerminalEvent(2, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + assertEquals(1000, to.valueCount()); + } + connection.dispose(); + } + } + } + + @Test + public void preNextConnect() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + + co.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.test(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void connectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.connect(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void selectorCrash() { + Observable.just(1).publish(new Function<Observable<Integer>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Observable<Integer> v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void source() { + Observable<Integer> o = Observable.never(); + + assertSame(o, (((HasUpstreamObservableSource<?>)o.publish()).source())); + } + + @Test + public void connectThrows() { + ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + try { + co.connect(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + throw new TestException(); + } + }); + } catch (TestException ex) { + // expected + } + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + + final TestObserver<Integer> to = co.test(); + + final TestObserver<Integer> to2 = new TestObserver<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.subscribe(to2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeOnArrival() { + ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + + co.test(true).assertEmpty(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Observable.never().publish()); + + TestHelper.checkDisposed(Observable.never().publish(Functions.<Observable<Object>>identity())); + } + + @Test + public void empty() { + ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); + + co.connect(); + } + + @Test + public void take() { + ConnectableObservable<Integer> co = Observable.range(1, 2).publish(); + + TestObserver<Integer> to = co.take(1).test(); + + co.connect(); + + to.assertResult(1); + } + + @Test + public void just() { + final PublishSubject<Integer> ps = PublishSubject.create(); + + ConnectableObservable<Integer> co = ps.publish(); + + TestObserver<Integer> to = new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ps.onComplete(); + } + }; + + co.subscribe(to); + co.connect(); + + ps.onNext(1); + + to.assertResult(1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final ConnectableObservable<Integer> co = ps.publish(); + + final TestObserver<Integer> to = co.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void badSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .publish() + .autoConnect() + .test() + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void noErrorLoss() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + ConnectableObservable<Object> co = Observable.error(new TestException()).publish(); + + co.connect(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void subscribeDisconnectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final ConnectableObservable<Integer> co = ps.publish(); + + final Disposable d = co.connect(); + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + d.dispose(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + co.subscribe(to); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void selectorDisconnectsIndependentSource() { + PublishSubject<Integer> ps = PublishSubject.create(); + + ps.publish(new Function<Observable<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { + return Observable.range(1, 2); + } + }) + .test() + .assertResult(1, 2); + + assertFalse(ps.hasObservers()); + } + + @Test(timeout = 5000) + public void selectorLatecommer() { + Observable.range(1, 5) + .publish(new Function<Observable<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { + return v.concatWith(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .publish(Functions.<Observable<Object>>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorInnerError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + ps.publish(new Function<Observable<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { + return Observable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void delayedUpstreamOnSubscribe() { + final Observer<?>[] sub = { null }; + + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + sub[0] = observer; + } + } + .publish() + .connect() + .dispose(); + + Disposable bs = Disposables.empty(); + + sub[0].onSubscribe(bs); + + assertTrue(bs.isDisposed()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(final Observable<Object> o) + throws Exception { + return Observable.<Integer>never().publish(new Function<Observable<Integer>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Observable<Integer> v) + throws Exception { + return o; + } + }); + } + } + ); + } + + @Test + public void disposedUpfront() { + ConnectableObservable<Integer> co = Observable.just(1) + .concatWith(Observable.<Integer>never()) + .publish(); + + TestObserver<Integer> to1 = co.test(); + + TestObserver<Integer> to2 = co.test(true); + + co.connect(); + + to1.assertValuesOnly(1); + + to2.assertEmpty(); + + ((ObservablePublish<Integer>)co).current.get().remove(null); + } + + @Test + public void altConnectCrash() { + try { + new ObservablePublishAlt<Integer>(Observable.<Integer>empty()) + .connect(new Consumer<Disposable>() { + @Override + public void accept(Disposable t) throws Exception { + throw new TestException(); + } + }); + fail("Should have thrown"); + } catch (TestException expected) { + // expected + } + } + + @Test + public void altConnectRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ConnectableObservable<Integer> co = + new ObservablePublishAlt<Integer>(Observable.<Integer>never()); + + Runnable r = new Runnable() { + @Override + public void run() { + co.connect(); + } + }; + + TestHelper.race(r, r); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java index 695f9c8e00..7534f07346 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import io.reactivex.*; import io.reactivex.Observable; @@ -37,6 +37,27 @@ public class ObservablePublishTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableObservableAssembly(new Function<ConnectableObservable, ConnectableObservable>() { + @Override + public ConnectableObservable apply(ConnectableObservable co) throws Exception { + if (co instanceof ObservablePublishAlt) { + return ObservablePublish.create(((ObservablePublishAlt)co).source()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableObservableAssembly(null); + } + @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); @@ -79,14 +100,14 @@ public void accept(String v) { } }); - Disposable s = o.connect(); + Disposable connection = o.connect(); try { if (!latch.await(1000, TimeUnit.MILLISECONDS)) { fail("subscriptions did not receive values"); } assertEquals(1, counter.get()); } finally { - s.dispose(); + connection.dispose(); } } @@ -125,12 +146,12 @@ public void run() { }); - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.merge(fast, slow).subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.merge(fast, slow).subscribe(to); is.connect(); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 4, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, to.valueCount()); } // use case from https://github.com/ReactiveX/RxJava/issues/1732 @@ -145,7 +166,7 @@ public void accept(Integer t1) { } }); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); xs.publish(new Function<Observable<Integer>, Observable<Integer>>() { @Override @@ -160,19 +181,19 @@ public boolean test(Integer i) { })); } - }).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - ts.assertValues(0, 1, 2, 3); + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertValues(0, 1, 2, 3); assertEquals(5, emitted.get()); - System.out.println(ts.values()); + System.out.println(to.values()); } // use case from https://github.com/ReactiveX/RxJava/issues/1732 @Test public void testTakeUntilWithPublishedStream() { Observable<Integer> xs = Observable.range(0, Flowable.bufferSize() * 2); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); ConnectableObservable<Integer> xsp = xs.publish(); xsp.takeUntil(xsp.skipWhile(new Predicate<Integer>() { @@ -181,9 +202,9 @@ public boolean test(Integer i) { return i <= 3; } - })).subscribe(ts); + })).subscribe(to); xsp.connect(); - System.out.println(ts.values()); + System.out.println(to.values()); } @Test(timeout = 10000) @@ -208,9 +229,9 @@ public void run() { final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); - final TestObserver<Integer> ts2 = new TestObserver<Integer>(); + final TestObserver<Integer> to2 = new TestObserver<Integer>(); - final TestObserver<Integer> ts1 = new TestObserver<Integer>() { + final TestObserver<Integer> to1 = new TestObserver<Integer>() { @Override public void onNext(Integer t) { if (valueCount() == 2) { @@ -219,7 +240,7 @@ public void onNext(Integer t) { public void run() { child2Unsubscribed.set(true); } - }).take(5).subscribe(ts2); + }).take(5).subscribe(to2); } super.onNext(t); } @@ -231,20 +252,20 @@ public void run() { child1Unsubscribed.set(true); } }).take(5) - .subscribe(ts1); + .subscribe(to1); - ts1.awaitTerminalEvent(); - ts2.awaitTerminalEvent(); + to1.awaitTerminalEvent(); + to2.awaitTerminalEvent(); - ts1.assertNoErrors(); - ts2.assertNoErrors(); + to1.assertNoErrors(); + to2.assertNoErrors(); assertTrue(sourceUnsubscribed.get()); assertTrue(child1Unsubscribed.get()); assertTrue(child2Unsubscribed.get()); - ts1.assertValues(1, 2, 3, 4, 5); - ts2.assertValues(4, 5, 6, 7, 8); + to1.assertValues(1, 2, 3, 4, 5); + to2.assertValues(4, 5, 6, 7, 8); assertEquals(8, sourceEmission.get()); } @@ -269,47 +290,47 @@ public void testConnectWithNoSubscriber() { public void testSubscribeAfterDisconnectThenConnect() { ConnectableObservable<Integer> source = Observable.just(1).publish(); - TestObserver<Integer> ts1 = new TestObserver<Integer>(); + TestObserver<Integer> to1 = new TestObserver<Integer>(); - source.subscribe(ts1); + source.subscribe(to1); - Disposable s = source.connect(); + Disposable connection = source.connect(); - ts1.assertValue(1); - ts1.assertNoErrors(); - ts1.assertTerminated(); + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>(); - source.subscribe(ts2); + source.subscribe(to2); - Disposable s2 = source.connect(); + Disposable connection2 = source.connect(); - ts2.assertValue(1); - ts2.assertNoErrors(); - ts2.assertTerminated(); + to2.assertValue(1); + to2.assertNoErrors(); + to2.assertTerminated(); - System.out.println(s); - System.out.println(s2); + System.out.println(connection); + System.out.println(connection2); } @Test public void testNoSubscriberRetentionOnCompleted() { ObservablePublish<Integer> source = (ObservablePublish<Integer>)Observable.just(1).publish(); - TestObserver<Integer> ts1 = new TestObserver<Integer>(); + TestObserver<Integer> to1 = new TestObserver<Integer>(); - source.subscribe(ts1); + source.subscribe(to1); - ts1.assertNoValues(); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertNoValues(); + to1.assertNoErrors(); + to1.assertNotComplete(); source.connect(); - ts1.assertValue(1); - ts1.assertNoErrors(); - ts1.assertTerminated(); + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); assertNull(source.current.get()); } @@ -326,18 +347,18 @@ public void testNonNullConnection() { public void testNoDisconnectSomeoneElse() { ConnectableObservable<Object> source = Observable.never().publish(); - Disposable s1 = source.connect(); - Disposable s2 = source.connect(); + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); - s1.dispose(); + connection1.dispose(); - Disposable s3 = source.connect(); + Disposable connection3 = source.connect(); - s2.dispose(); + connection2.dispose(); - assertTrue(checkPublishDisposed(s1)); - assertTrue(checkPublishDisposed(s2)); - assertFalse(checkPublishDisposed(s3)); + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); } @SuppressWarnings("unchecked") @@ -372,35 +393,36 @@ public void subscribe(Observer<? super Integer> t) { assertEquals(2, calls.get()); } + @Test public void testObserveOn() { ConnectableObservable<Integer> co = Observable.range(0, 1000).publish(); Observable<Integer> obs = co.observeOn(Schedulers.computation()); for (int i = 0; i < 1000; i++) { for (int j = 1; j < 6; j++) { - List<TestObserver<Integer>> tss = new ArrayList<TestObserver<Integer>>(); + List<TestObserver<Integer>> tos = new ArrayList<TestObserver<Integer>>(); for (int k = 1; k < j; k++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); - tss.add(ts); - obs.subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + tos.add(to); + obs.subscribe(to); } - Disposable s = co.connect(); + Disposable connection = co.connect(); - for (TestObserver<Integer> ts : tss) { - ts.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts.assertTerminated(); - ts.assertNoErrors(); - assertEquals(1000, ts.valueCount()); + for (TestObserver<Integer> to : tos) { + to.awaitTerminalEvent(2, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + assertEquals(1000, to.valueCount()); } - s.dispose(); + connection.dispose(); } } } @Test public void preNextConnect() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); @@ -419,7 +441,7 @@ public void run() { @Test public void connectRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); @@ -459,7 +481,7 @@ public void connectThrows() { try { co.connect(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException(); } }); @@ -470,7 +492,7 @@ public void accept(Disposable s) throws Exception { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.<Integer>empty().publish(); @@ -552,7 +574,7 @@ public void onNext(Integer t) { @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -620,7 +642,7 @@ public void noErrorLoss() { @Test public void subscribeDisconnectRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); @@ -706,8 +728,8 @@ public void delayedUpstreamOnSubscribe() { new Observable<Integer>() { @Override - protected void subscribeActual(Observer<? super Integer> s) { - sub[0] = s; + protected void subscribeActual(Observer<? super Integer> observer) { + sub[0] = observer; } } .publish() @@ -720,4 +742,41 @@ protected void subscribeActual(Observer<? super Integer> s) { assertTrue(bs.isDisposed()); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(final Observable<Object> o) + throws Exception { + return Observable.<Integer>never().publish(new Function<Observable<Integer>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Observable<Integer> v) + throws Exception { + return o; + } + }); + } + } + ); + } + + @Test + public void disposedUpfront() { + ConnectableObservable<Integer> co = Observable.just(1) + .concatWith(Observable.<Integer>never()) + .publish(); + + TestObserver<Integer> to1 = co.test(); + + TestObserver<Integer> to2 = co.test(true); + + co.connect(); + + to1.assertValuesOnly(1); + + to2.assertEmpty(); + + ((ObservablePublish<Integer>)co).current.get().remove(null); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java index e06da6205b..dfe1713902 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; @@ -24,7 +23,7 @@ import io.reactivex.*; import io.reactivex.functions.Consumer; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.observers.*; public class ObservableRangeLongTest { @@ -99,12 +98,12 @@ public void testNoBackpressure() { Observable<Long> o = Observable.rangeLong(1, list.size()); - TestObserver<Long> ts = new TestObserver<Long>(); + TestObserver<Long> to = new TestObserver<Long>(); - o.subscribe(ts); + o.subscribe(to); - ts.assertValueSequence(list); - ts.assertTerminated(); + to.assertValueSequence(list); + to.assertTerminated(); } @Test @@ -136,12 +135,12 @@ public void onNext(Long t) { @Test(timeout = 1000) public void testNearMaxValueWithoutBackpressure() { - TestObserver<Long> ts = new TestObserver<Long>(); - Observable.rangeLong(Long.MAX_VALUE - 1L, 2L).subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + Observable.rangeLong(Long.MAX_VALUE - 1L, 2L).subscribe(to); - ts.assertComplete(); - ts.assertNoErrors(); - ts.assertValues(Long.MAX_VALUE - 1, Long.MAX_VALUE); + to.assertComplete(); + to.assertNoErrors(); + to.assertValues(Long.MAX_VALUE - 1, Long.MAX_VALUE); } @Test @@ -170,21 +169,21 @@ public void noOverflow() { @Test public void fused() { - TestObserver<Long> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Long> to = ObserverFusion.newTest(QueueFuseable.ANY); Observable.rangeLong(1, 2).subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.SYNC) + ObserverFusion.assertFusion(to, QueueFuseable.SYNC) .assertResult(1L, 2L); } @Test public void fusedReject() { - TestObserver<Long> to = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Long> to = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.rangeLong(1, 2).subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1L, 2L); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java index 3fe8a047f0..d54847c0b2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java @@ -23,7 +23,7 @@ import io.reactivex.*; import io.reactivex.functions.Consumer; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; public class ObservableRangeTest { @@ -99,12 +99,12 @@ public void testNoBackpressure() { Observable<Integer> o = Observable.range(1, list.size()); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - o.subscribe(ts); + o.subscribe(to); - ts.assertValueSequence(list); - ts.assertTerminated(); + to.assertValueSequence(list); + to.assertTerminated(); } @Test @@ -136,12 +136,12 @@ public void onNext(Integer t) { @Test(timeout = 1000) public void testNearMaxValueWithoutBackpressure() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.range(Integer.MAX_VALUE - 1, 2).subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.range(Integer.MAX_VALUE - 1, 2).subscribe(to); - ts.assertComplete(); - ts.assertNoErrors(); - ts.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); + to.assertComplete(); + to.assertNoErrors(); + to.assertValues(Integer.MAX_VALUE - 1, Integer.MAX_VALUE); } @Test @@ -156,12 +156,12 @@ public void negativeCount() { @Test public void requestWrongFusion() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ASYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ASYNC); Observable.range(1, 5) .subscribe(to); - ObserverFusion.assertFusion(to, QueueDisposable.NONE) + ObserverFusion.assertFusion(to, QueueFuseable.NONE) .assertResult(1, 2, 3, 4, 5); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java index 5faf25b837..c1c7ffbeb6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java @@ -16,11 +16,17 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; +import java.util.List; +import java.util.concurrent.Callable; + import org.junit.*; import io.reactivex.*; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.PublishSubject; public class ObservableReduceTest { Observer<Object> observer; @@ -138,7 +144,6 @@ public void testBackpressureWithInitialValueObservable() throws InterruptedExcep assertEquals(21, r.intValue()); } - @Test public void testAggregateAsIntSum() { @@ -234,5 +239,130 @@ public void testBackpressureWithInitialValue() throws InterruptedException { assertEquals(21, r.intValue()); } + @Test + public void reduceWithSingle() { + Observable.range(1, 5) + .reduceWith(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return 0; + } + }, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a + b; + } + }) + .test() + .assertResult(15); + } + + @Test + public void reduceMaybeDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservableToMaybe(new Function<Observable<Object>, MaybeSource<Object>>() { + @Override + public MaybeSource<Object> apply(Observable<Object> o) + throws Exception { + return o.reduce(new BiFunction<Object, Object, Object>() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + }); + } + }); + } + + @Test + public void reduceMaybeCheckDisposed() { + TestHelper.checkDisposed(Observable.just(new Object()).reduce(new BiFunction<Object, Object, Object>() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + })); + } + + @Test + public void reduceMaybeBadSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onNext(1); + observer.onError(new TestException()); + observer.onComplete(); + } + }.reduce(new BiFunction<Object, Object, Object>() { + @Override + public Object apply(Object a, Object b) throws Exception { + return a; + } + }) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + @Test + public void seedDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservableToSingle(new Function<Observable<Integer>, SingleSource<Integer>>() { + @Override + public SingleSource<Integer> apply(Observable<Integer> o) + throws Exception { + return o.reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + }); + } + }); + } + + @Test + public void seedDisposed() { + TestHelper.checkDisposed(PublishSubject.<Integer>create().reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + })); + } + + @Test + public void seedBadSource() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onNext(1); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .reduce(0, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer a, Integer b) throws Exception { + return a; + } + }) + .test() + .assertResult(0); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java new file mode 100644 index 0000000000..0f675bd15d --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java @@ -0,0 +1,1421 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableRefCount.RefConnection; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; +import io.reactivex.subjects.*; + +public class ObservableRefCountAltTest { + + @Test + public void testRefCountAsync() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Observable<Long> r = Observable.interval(0, 25, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer<Long>() { + @Override + public void accept(Long l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer<Long>() { + @Override + public void accept(Long l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(260); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); + + System.out.println("onNext: " + nextCount.get()); + + // should emit once for both subscribers + assertEquals(nextCount.get(), receivedCount.get()); + // only 1 subscribe + assertEquals(1, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronous() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Observable<Integer> r = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); + + System.out.println("onNext Count: " + nextCount.get()); + + // it will emit twice because it is synchronous + assertEquals(nextCount.get(), receivedCount.get() * 2); + // it will subscribe twice because it is synchronous + assertEquals(2, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronousTake() { + final AtomicInteger nextCount = new AtomicInteger(); + Observable<Integer> r = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + System.out.println("onNext --------> " + l); + nextCount.incrementAndGet(); + } + }) + .take(4) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + r.subscribe(new Consumer<Integer>() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + System.out.println("onNext: " + nextCount.get()); + + assertEquals(4, receivedCount.get()); + assertEquals(4, receivedCount.get()); + } + + @Test + public void testRepeat() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger unsubscribeCount = new AtomicInteger(); + Observable<Long> r = Observable.interval(0, 1, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeCount.incrementAndGet(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeCount.incrementAndGet(); + } + }) + .publish().refCount(); + + for (int i = 0; i < 10; i++) { + TestObserver<Long> to1 = new TestObserver<Long>(); + TestObserver<Long> to2 = new TestObserver<Long>(); + r.subscribe(to1); + r.subscribe(to2); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + to1.dispose(); + to2.dispose(); + to1.assertNoErrors(); + to2.assertNoErrors(); + assertTrue(to1.valueCount() > 0); + assertTrue(to2.valueCount() > 0); + } + + assertEquals(10, subscribeCount.get()); + assertEquals(10, unsubscribeCount.get()); + } + + @Test + public void testConnectUnsubscribe() throws InterruptedException { + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final CountDownLatch subscribeLatch = new CountDownLatch(1); + + Observable<Long> o = synchronousInterval() + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeLatch.countDown(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeLatch.countDown(); + } + }); + + TestObserver<Long> observer = new TestObserver<Long>(); + o.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(observer); + System.out.println("send unsubscribe"); + // wait until connected + subscribeLatch.await(); + // now unsubscribe + observer.dispose(); + System.out.println("DONE sending unsubscribe ... now waiting"); + if (!unsubscribeLatch.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size() > 0) { + observer.errors().get(0).printStackTrace(); + } + fail("timed out waiting for unsubscribe"); + } + observer.assertNoErrors(); + } + + @Test + public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedException { + for (int i = 0; i < 100; i++) { + testConnectUnsubscribeRaceCondition(); + } + } + + @Test + public void testConnectUnsubscribeRaceCondition() throws InterruptedException { + final AtomicInteger subUnsubCount = new AtomicInteger(); + Observable<Long> o = synchronousInterval() + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + subUnsubCount.decrementAndGet(); + } + }) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* SUBSCRIBE received"); + subUnsubCount.incrementAndGet(); + } + }); + + TestObserver<Long> observer = new TestObserver<Long>(); + + o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(observer); + System.out.println("send unsubscribe"); + // now immediately unsubscribe while subscribeOn is racing to subscribe + observer.dispose(); + + // this generally will mean it won't even subscribe as it is already unsubscribed by the time connect() gets scheduled + // give time to the counter to update + Thread.sleep(10); + + // make sure we wait a bit in case the counter is still nonzero + int counter = 200; + while (subUnsubCount.get() != 0 && counter-- != 0) { + Thread.sleep(10); + } + // either we subscribed and then unsubscribed, or we didn't ever even subscribe + assertEquals(0, subUnsubCount.get()); + + System.out.println("DONE sending unsubscribe ... now waiting"); + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size() > 0) { + observer.errors().get(0).printStackTrace(); + } + observer.assertNoErrors(); + } + + private Observable<Long> synchronousInterval() { + return Observable.unsafeCreate(new ObservableSource<Long>() { + @Override + public void subscribe(Observer<? super Long> observer) { + final AtomicBoolean cancel = new AtomicBoolean(); + observer.onSubscribe(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + cancel.set(true); + } + })); + for (;;) { + if (cancel.get()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + observer.onNext(1L); + } + } + }); + } + + @Test + public void onlyFirstShouldSubscribeAndLastUnsubscribe() { + final AtomicInteger subscriptionCount = new AtomicInteger(); + final AtomicInteger unsubscriptionCount = new AtomicInteger(); + Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() { + @Override + public void subscribe(Observer<? super Integer> observer) { + subscriptionCount.incrementAndGet(); + observer.onSubscribe(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + unsubscriptionCount.incrementAndGet(); + } + })); + } + }); + Observable<Integer> refCounted = o.publish().refCount(); + + Disposable first = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + Disposable second = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + first.dispose(); + assertEquals(0, unsubscriptionCount.get()); + + second.dispose(); + assertEquals(1, unsubscriptionCount.get()); + } + + @Test + public void testRefCount() { + TestScheduler s = new TestScheduler(); + Observable<Long> interval = Observable.interval(100, TimeUnit.MILLISECONDS, s).publish().refCount(); + + // subscribe list1 + final List<Long> list1 = new ArrayList<Long>(); + Disposable d1 = interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list1.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list1.size()); + assertEquals(0L, list1.get(0).longValue()); + assertEquals(1L, list1.get(1).longValue()); + + // subscribe list2 + final List<Long> list2 = new ArrayList<Long>(); + Disposable d2 = interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list2.add(t1); + } + }); + + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should have 5 items + assertEquals(5, list1.size()); + assertEquals(2L, list1.get(2).longValue()); + assertEquals(3L, list1.get(3).longValue()); + assertEquals(4L, list1.get(4).longValue()); + + // list 2 should only have 3 items + assertEquals(3, list2.size()); + assertEquals(2L, list2.get(0).longValue()); + assertEquals(3L, list2.get(1).longValue()); + assertEquals(4L, list2.get(2).longValue()); + + // unsubscribe list1 + d1.dispose(); + + // advance further + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should still have 5 items + assertEquals(5, list1.size()); + + // list 2 should have 6 items + assertEquals(6, list2.size()); + assertEquals(5L, list2.get(3).longValue()); + assertEquals(6L, list2.get(4).longValue()); + assertEquals(7L, list2.get(5).longValue()); + + // unsubscribe list2 + d2.dispose(); + + // advance further + s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + // subscribing a new one should start over because the source should have been unsubscribed + // subscribe list3 + final List<Long> list3 = new ArrayList<Long>(); + interval.subscribe(new Consumer<Long>() { + @Override + public void accept(Long t1) { + list3.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list3.size()); + assertEquals(0L, list3.get(0).longValue()); + assertEquals(1L, list3.get(1).longValue()); + + } + + @Test + public void testAlreadyUnsubscribedClient() { + Observer<Integer> done = DisposingObserver.INSTANCE; + + Observer<Integer> o = TestHelper.mockObserver(); + + Observable<Integer> result = Observable.just(1).publish().refCount(); + + result.subscribe(done); + + result.subscribe(o); + + verify(o).onNext(1); + verify(o).onComplete(); + verify(o, never()).onError(any(Throwable.class)); + } + + @Test + public void testAlreadyUnsubscribedInterleavesWithClient() { + ReplaySubject<Integer> source = ReplaySubject.create(); + + Observer<Integer> done = DisposingObserver.INSTANCE; + + Observer<Integer> o = TestHelper.mockObserver(); + InOrder inOrder = inOrder(o); + + Observable<Integer> result = source.publish().refCount(); + + result.subscribe(o); + + source.onNext(1); + + result.subscribe(done); + + source.onNext(2); + source.onComplete(); + + inOrder.verify(o).onNext(1); + inOrder.verify(o).onNext(2); + inOrder.verify(o).onComplete(); + verify(o, never()).onError(any(Throwable.class)); + } + + @Test + public void testConnectDisconnectConnectAndSubjectState() { + Observable<Integer> o1 = Observable.just(10); + Observable<Integer> o2 = Observable.just(20); + Observable<Integer> combined = Observable.combineLatest(o1, o2, new BiFunction<Integer, Integer, Integer>() { + @Override + public Integer apply(Integer t1, Integer t2) { + return t1 + t2; + } + }) + .publish().refCount(); + + TestObserver<Integer> to1 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>(); + + combined.subscribe(to1); + combined.subscribe(to2); + + to1.assertTerminated(); + to1.assertNoErrors(); + to1.assertValue(30); + + to2.assertTerminated(); + to2.assertNoErrors(); + to2.assertValue(30); + } + + @Test(timeout = 10000) + public void testUpstreamErrorAllowsRetry() throws InterruptedException { + final AtomicInteger intervalSubscribed = new AtomicInteger(); + Observable<String> interval = + Observable.interval(200, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) { + System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); + } + } + ) + .flatMap(new Function<Long, Observable<String>>() { + @Override + public Observable<String> apply(Long t1) { + return Observable.defer(new Callable<Observable<String>>() { + @Override + public Observable<String> call() { + return Observable.<String>error(new Exception("Some exception")); + } + }); + } + }) + .onErrorResumeNext(new Function<Throwable, Observable<String>>() { + @Override + public Observable<String> apply(Throwable t1) { + return Observable.<String>error(t1); + } + }) + .publish() + .refCount(); + + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Observer 1 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Observer 1: " + t1); + } + }); + Thread.sleep(100); + interval + .doOnError(new Consumer<Throwable>() { + @Override + public void accept(Throwable t1) { + System.out.println("Observer 2 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer<String>() { + @Override + public void accept(String t1) { + System.out.println("Observer 2: " + t1); + } + }); + + Thread.sleep(1300); + + System.out.println(intervalSubscribed.get()); + assertEquals(6, intervalSubscribed.get()); + } + + private enum DisposingObserver implements Observer<Integer> { + INSTANCE; + + @Override + public void onSubscribe(Disposable d) { + d.dispose(); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Observable.just(1).publish().refCount()); + } + + @Test + public void noOpConnect() { + final int[] calls = { 0 }; + Observable<Integer> o = new ConnectableObservable<Integer>() { + @Override + public void connect(Consumer<? super Disposable> connection) { + calls[0]++; + } + + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.disposed()); + } + }.refCount(); + + o.test(); + o.test(); + + assertEquals(1, calls[0]); + } + Observable<Object> source; + + @Test + public void replayNoLeak() throws Exception { + System.gc(); + Thread.sleep(250); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }) + .replay(1) + .refCount(); + + source.subscribe(); + + System.gc(); + Thread.sleep(250); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayNoLeak2() throws Exception { + System.gc(); + Thread.sleep(250); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .replay(1) + .refCount(); + + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + System.gc(); + Thread.sleep(250); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + static final class ExceptionData extends Exception { + private static final long serialVersionUID = -6763898015338136119L; + + public final Object data; + + ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + System.gc(); + Thread.sleep(250); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + throw new ExceptionData(new byte[100 * 1000 * 1000]); + } + }) + .publish() + .refCount(); + + source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + + long after = 0L; + + for (int i = 0; i < 10; i++) { + System.gc(); + + after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + if (start + 20 * 1000 * 1000 > after) { + break; + } + + Thread.sleep(100); + } + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void publishNoLeak2() throws Exception { + System.gc(); + Thread.sleep(250); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .publish() + .refCount(); + + Disposable d1 = source.test(); + Disposable d2 = source.test(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + System.gc(); + Thread.sleep(250); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayIsUnsubscribed() { + ConnectableObservable<Integer> co = Observable.just(1).concatWith(Observable.<Integer>never()) + .replay(); + + if (co instanceof Disposable) { + assertTrue(((Disposable)co).isDisposed()); + + Disposable connection = co.connect(); + + assertFalse(((Disposable)co).isDisposed()); + + connection.dispose(); + + assertTrue(((Disposable)co).isDisposed()); + } + } + + static final class BadObservableSubscribe extends ConnectableObservable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + throw new TestException("subscribeActual"); + } + } + + static final class BadObservableDispose extends ConnectableObservable<Object> implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + static final class BadObservableConnect extends ConnectableObservable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + @Test + public void badSourceSubscribe() { + BadObservableSubscribe bo = new BadObservableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test + public void badSourceDispose() { + BadObservableDispose bo = new BadObservableDispose(); + + try { + bo.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + BadObservableConnect bo = new BadObservableConnect(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableSubscribe2 extends ConnectableObservable<Object> { + + int count; + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + if (++count == 1) { + observer.onSubscribe(Disposables.empty()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + BadObservableSubscribe2 bo = new BadObservableSubscribe2(); + + Observable<Object> o = bo.refCount(); + o.test(); + try { + o.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableConnect2 extends ConnectableObservable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + BadObservableConnect2 bo = new BadObservableConnect2(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorSubject<Integer> bs = BehaviorSubject.createDefault(1); + + Observable<Integer> o = bs + .replay(1) + .refCount(); + + o.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + o.switchMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) throws Exception { + return Observable.create(new ObservableOnSubscribe<Object>() { + @Override + public void subscribe(ObservableEmitter<Object> emitter) throws Exception { + while (!emitter.isDisposed()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }); + } + }) + .take(500, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Observable<Integer> source = Observable.range(1, 5) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestObserver<Integer> to1 = source.test(); + + to1.withTag("to1 " + i); + to1.assertEmpty(); + + TestObserver<Integer> to2 = source.test(); + + to2.withTag("to2 " + i); + + to1.assertResult(1, 2, 3, 4, 5); + to2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestObserver<Integer> to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + Thread.sleep(100); + + to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + ps.onNext(5); + ps.onComplete(); + + to1 + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestObserver<Integer> to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + assertTrue(ps.hasObservers()); + + Thread.sleep(200); + + assertFalse(ps.hasObservers()); + } + + @Test + public void error() { + Observable.<Integer>error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .publish() + .refCount(1); + + TestObserver<Integer> to1 = source.test(); + + assertTrue(ps.hasObservers()); + + for (int i = 0; i < 3; i++) { + TestObserver<Integer> to2 = source.test(); + to1.cancel(); + to1 = to2; + } + + to1.cancel(); + + assertFalse(ps.hasObservers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { + + final Observable<Integer> source = Observable.range(1, 5) + .replay() + .refCount(1) + ; + + final TestObserver<Integer> to1 = source.test(); + + final TestObserver<Integer> to2 = new TestObserver<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(to2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to2 + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadObservableDoubleOnX extends ConnectableObservable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onComplete(); + observer.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + ObservableRefCount<Object> o = (ObservableRefCount<Object>)PublishSubject.create() + .publish() + .refCount(); + + o.cancel(null); + + o.cancel(new RefConnection(o)); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + rc.lazySet(null); + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Observable<Integer> observable = Observable.just(1).replay(1).refCount(); + + TestObserver<Integer> observer1 = observable + .subscribeOn(Schedulers.io()) + .test(); + + TestObserver<Integer> observer2 = observable + .subscribeOn(Schedulers.io()) + .test(); + + observer1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + observer2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableObservable<T> extends ConnectableObservable<T> + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + // not relevant + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + ObservableRefCount<Object> o = (ObservableRefCount<Object>)new TestConnectableObservable<Object>().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorSubject<Integer> subject = BehaviorSubject.create(); + + Observable<Integer> observable = subject + .replay(1) + .refCount(); + + observable.takeUntil(Observable.just(1)).test(); + + subject.onNext(2); + + observable.take(1).test().assertResult(2); + } + + @Test + public void publishRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Observable<Integer> observable = Observable.just(1).publish().refCount(); + + TestObserver<Integer> observer1 = observable + .subscribeOn(Schedulers.io()) + .test(); + + TestObserver<Integer> observer2 = observable + .subscribeOn(Schedulers.io()) + .test(); + + observer1 + .withTag("observer1 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + + observer2 + .withTag("observer2 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + } + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplaySubject<Integer> rs = ReplaySubject.create(); + rs.onNext(1); + rs.onComplete(); + + Observable<Integer> shared = rs.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java index d30435651c..485afc54e1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java @@ -14,30 +14,55 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableRefCount.RefConnection; +import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.observables.ConnectableObservable; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; -import io.reactivex.subjects.ReplaySubject; +import io.reactivex.subjects.*; public class ObservableRefCountTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableObservableAssembly(new Function<ConnectableObservable, ConnectableObservable>() { + @Override + public ConnectableObservable apply(ConnectableObservable co) throws Exception { + if (co instanceof ObservablePublishAlt) { + return ObservablePublish.create(((ObservablePublishAlt)co).source()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableObservableAssembly(null); + } + @Test public void testRefCountAsync() { final AtomicInteger subscribeCount = new AtomicInteger(); @@ -45,7 +70,7 @@ public void testRefCountAsync() { Observable<Long> r = Observable.interval(0, 25, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { subscribeCount.incrementAndGet(); } }) @@ -58,14 +83,14 @@ public void accept(Long l) { .publish().refCount(); final AtomicInteger receivedCount = new AtomicInteger(); - Disposable s1 = r.subscribe(new Consumer<Long>() { + Disposable d1 = r.subscribe(new Consumer<Long>() { @Override public void accept(Long l) { receivedCount.incrementAndGet(); } }); - Disposable s2 = r.subscribe(); + Disposable d2 = r.subscribe(); // give time to emit try { @@ -74,8 +99,8 @@ public void accept(Long l) { } // now unsubscribe - s2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other - s1.dispose(); + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); System.out.println("onNext: " + nextCount.get()); @@ -92,7 +117,7 @@ public void testRefCountSynchronous() { Observable<Integer> r = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { subscribeCount.incrementAndGet(); } }) @@ -105,14 +130,14 @@ public void accept(Integer l) { .publish().refCount(); final AtomicInteger receivedCount = new AtomicInteger(); - Disposable s1 = r.subscribe(new Consumer<Integer>() { + Disposable d1 = r.subscribe(new Consumer<Integer>() { @Override public void accept(Integer l) { receivedCount.incrementAndGet(); } }); - Disposable s2 = r.subscribe(); + Disposable d2 = r.subscribe(); // give time to emit try { @@ -121,8 +146,8 @@ public void accept(Integer l) { } // now unsubscribe - s2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other - s1.dispose(); + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); System.out.println("onNext Count: " + nextCount.get()); @@ -167,7 +192,7 @@ public void testRepeat() { Observable<Long> r = Observable.interval(0, 1, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { System.out.println("******************************* Subscribe received"); // when we are subscribed subscribeCount.incrementAndGet(); @@ -184,20 +209,20 @@ public void run() { .publish().refCount(); for (int i = 0; i < 10; i++) { - TestObserver<Long> ts1 = new TestObserver<Long>(); - TestObserver<Long> ts2 = new TestObserver<Long>(); - r.subscribe(ts1); - r.subscribe(ts2); + TestObserver<Long> to1 = new TestObserver<Long>(); + TestObserver<Long> to2 = new TestObserver<Long>(); + r.subscribe(to1); + r.subscribe(to2); try { Thread.sleep(50); } catch (InterruptedException e) { } - ts1.dispose(); - ts2.dispose(); - ts1.assertNoErrors(); - ts2.assertNoErrors(); - assertTrue(ts1.valueCount() > 0); - assertTrue(ts2.valueCount() > 0); + to1.dispose(); + to2.dispose(); + to1.assertNoErrors(); + to2.assertNoErrors(); + assertTrue(to1.valueCount() > 0); + assertTrue(to2.valueCount() > 0); } assertEquals(10, subscribeCount.get()); @@ -212,7 +237,7 @@ public void testConnectUnsubscribe() throws InterruptedException { Observable<Long> o = synchronousInterval() .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { System.out.println("******************************* Subscribe received"); // when we are subscribed subscribeLatch.countDown(); @@ -227,22 +252,22 @@ public void run() { } }); - TestObserver<Long> s = new TestObserver<Long>(); - o.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(s); + TestObserver<Long> observer = new TestObserver<Long>(); + o.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(observer); System.out.println("send unsubscribe"); // wait until connected subscribeLatch.await(); // now unsubscribe - s.dispose(); + observer.dispose(); System.out.println("DONE sending unsubscribe ... now waiting"); if (!unsubscribeLatch.await(3000, TimeUnit.MILLISECONDS)) { - System.out.println("Errors: " + s.errors()); - if (s.errors().size() > 0) { - s.errors().get(0).printStackTrace(); + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size() > 0) { + observer.errors().get(0).printStackTrace(); } fail("timed out waiting for unsubscribe"); } - s.assertNoErrors(); + observer.assertNoErrors(); } @Test @@ -266,18 +291,18 @@ public void run() { }) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { System.out.println("******************************* SUBSCRIBE received"); subUnsubCount.incrementAndGet(); } }); - TestObserver<Long> s = new TestObserver<Long>(); + TestObserver<Long> observer = new TestObserver<Long>(); - o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); + o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(observer); System.out.println("send unsubscribe"); // now immediately unsubscribe while subscribeOn is racing to subscribe - s.dispose(); + observer.dispose(); // this generally will mean it won't even subscribe as it is already unsubscribed by the time connect() gets scheduled // give time to the counter to update @@ -292,11 +317,11 @@ public void accept(Disposable s) { assertEquals(0, subUnsubCount.get()); System.out.println("DONE sending unsubscribe ... now waiting"); - System.out.println("Errors: " + s.errors()); - if (s.errors().size() > 0) { - s.errors().get(0).printStackTrace(); + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size() > 0) { + observer.errors().get(0).printStackTrace(); } - s.assertNoErrors(); + observer.assertNoErrors(); } private Observable<Long> synchronousInterval() { @@ -362,7 +387,7 @@ public void testRefCount() { // subscribe list1 final List<Long> list1 = new ArrayList<Long>(); - Disposable s1 = interval.subscribe(new Consumer<Long>() { + Disposable d1 = interval.subscribe(new Consumer<Long>() { @Override public void accept(Long t1) { list1.add(t1); @@ -377,7 +402,7 @@ public void accept(Long t1) { // subscribe list2 final List<Long> list2 = new ArrayList<Long>(); - Disposable s2 = interval.subscribe(new Consumer<Long>() { + Disposable d2 = interval.subscribe(new Consumer<Long>() { @Override public void accept(Long t1) { list2.add(t1); @@ -399,7 +424,7 @@ public void accept(Long t1) { assertEquals(4L, list2.get(2).longValue()); // unsubscribe list1 - s1.dispose(); + d1.dispose(); // advance further s.advanceTimeBy(300, TimeUnit.MILLISECONDS); @@ -414,7 +439,7 @@ public void accept(Long t1) { assertEquals(7L, list2.get(5).longValue()); // unsubscribe list2 - s2.dispose(); + d2.dispose(); // advance further s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); @@ -492,29 +517,29 @@ public Integer apply(Integer t1, Integer t2) { }) .publish().refCount(); - TestObserver<Integer> ts1 = new TestObserver<Integer>(); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); + TestObserver<Integer> to1 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>(); - combined.subscribe(ts1); - combined.subscribe(ts2); + combined.subscribe(to1); + combined.subscribe(to2); - ts1.assertTerminated(); - ts1.assertNoErrors(); - ts1.assertValue(30); + to1.assertTerminated(); + to1.assertNoErrors(); + to1.assertValue(30); - ts2.assertTerminated(); - ts2.assertNoErrors(); - ts2.assertValue(30); + to2.assertTerminated(); + to2.assertNoErrors(); + to2.assertValue(30); } @Test(timeout = 10000) public void testUpstreamErrorAllowsRetry() throws InterruptedException { final AtomicInteger intervalSubscribed = new AtomicInteger(); Observable<String> interval = - Observable.interval(200,TimeUnit.MILLISECONDS) + Observable.interval(200, TimeUnit.MILLISECONDS) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); } } @@ -626,7 +651,7 @@ protected void subscribeActual(Observer<? super Integer> observer) { @Test public void replayNoLeak() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -642,7 +667,7 @@ public Object call() throws Exception { source.subscribe(); System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -653,7 +678,7 @@ public Object call() throws Exception { @Test public void replayNoLeak2() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -666,17 +691,17 @@ public Object call() throws Exception { .replay(1) .refCount(); - Disposable s1 = source.subscribe(); - Disposable s2 = source.subscribe(); + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); - s1.dispose(); - s2.dispose(); + d1.dispose(); + d2.dispose(); - s1 = null; - s2 = null; + d1 = null; + d2 = null; System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -697,7 +722,7 @@ static final class ExceptionData extends Exception { @Test public void publishNoLeak() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -713,7 +738,7 @@ public Object call() throws Exception { source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -724,7 +749,7 @@ public Object call() throws Exception { @Test public void publishNoLeak2() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -737,17 +762,17 @@ public Object call() throws Exception { .publish() .refCount(); - Disposable s1 = source.test(); - Disposable s2 = source.test(); + Disposable d1 = source.test(); + Disposable d2 = source.test(); - s1.dispose(); - s2.dispose(); + d1.dispose(); + d2.dispose(); - s1 = null; - s2 = null; + d1 = null; + d2 = null; System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -760,14 +785,618 @@ public void replayIsUnsubscribed() { ConnectableObservable<Integer> co = Observable.just(1).concatWith(Observable.<Integer>never()) .replay(); - assertTrue(((Disposable)co).isDisposed()); + if (co instanceof Disposable) { + assertTrue(((Disposable)co).isDisposed()); + + Disposable connection = co.connect(); + + assertFalse(((Disposable)co).isDisposed()); + + connection.dispose(); + + assertTrue(((Disposable)co).isDisposed()); + } + } + + static final class BadObservableSubscribe extends ConnectableObservable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + throw new TestException("subscribeActual"); + } + } + + static final class BadObservableDispose extends ConnectableObservable<Object> implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + static final class BadObservableConnect extends ConnectableObservable<Object> { + + @Override + public void connect(Consumer<? super Disposable> connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + @Test + public void badSourceSubscribe() { + BadObservableSubscribe bo = new BadObservableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test + public void badSourceDispose() { + BadObservableDispose bo = new BadObservableDispose(); + + try { + bo.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + BadObservableConnect bo = new BadObservableConnect(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableSubscribe2 extends ConnectableObservable<Object> { + + int count; + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + if (++count == 1) { + observer.onSubscribe(Disposables.empty()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + BadObservableSubscribe2 bo = new BadObservableSubscribe2(); + + Observable<Object> o = bo.refCount(); + o.test(); + try { + o.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableConnect2 extends ConnectableObservable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + BadObservableConnect2 bo = new BadObservableConnect2(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorSubject<Integer> bs = BehaviorSubject.createDefault(1); + + Observable<Integer> o = bs + .replay(1) + .refCount(); + + o.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + o.switchMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) throws Exception { + return Observable.create(new ObservableOnSubscribe<Object>() { + @Override + public void subscribe(ObservableEmitter<Object> emitter) throws Exception { + while (!emitter.isDisposed()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }); + } + }) + .take(500, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Observable<Integer> source = Observable.range(1, 5) + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestObserver<Integer> to1 = source.test(); + + to1.assertEmpty(); + + TestObserver<Integer> to2 = source.test(); + + to1.assertResult(1, 2, 3, 4, 5); + to2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestObserver<Integer> to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + Thread.sleep(100); + + to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + ps.onNext(5); + ps.onComplete(); + + to1 + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestObserver<Integer> to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + assertTrue(ps.hasObservers()); + + Thread.sleep(200); + + assertFalse(ps.hasObservers()); + } + + @Test + public void error() { + Observable.<Integer>error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishSubject<Integer> ps = PublishSubject.create(); + + Observable<Integer> source = ps + .publish() + .refCount(1); + + TestObserver<Integer> to1 = source.test(); + + assertTrue(ps.hasObservers()); + + for (int i = 0; i < 3; i++) { + TestObserver<Integer> to2 = source.test(); + to1.cancel(); + to1 = to2; + } + + to1.cancel(); + + assertFalse(ps.hasObservers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { - Disposable s = co.connect(); + final Observable<Integer> source = Observable.range(1, 5) + .replay() + .refCount(1) + ; - assertFalse(((Disposable)co).isDisposed()); + final TestObserver<Integer> to1 = source.test(); - s.dispose(); + final TestObserver<Integer> to2 = new TestObserver<Integer>(); - assertTrue(((Disposable)co).isDisposed()); + Runnable r1 = new Runnable() { + @Override + public void run() { + to1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(to2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to2 + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadObservableDoubleOnX extends ConnectableObservable<Object> + implements Disposable { + + @Override + public void connect(Consumer<? super Disposable> connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onComplete(); + observer.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + ObservableRefCount<Object> o = (ObservableRefCount<Object>)PublishSubject.create() + .publish() + .refCount(); + + o.cancel(null); + + o.cancel(new RefConnection(o)); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Observable<Integer> observable = Observable.just(1).replay(1).refCount(); + + TestObserver<Integer> observer1 = observable + .subscribeOn(Schedulers.io()) + .test(); + + TestObserver<Integer> observer2 = observable + .subscribeOn(Schedulers.io()) + .test(); + + observer1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + observer2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableObservable<T> extends ConnectableObservable<T> + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer<? super Disposable> connection) { + // not relevant + } + + @Override + protected void subscribeActual(Observer<? super T> observer) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + ObservableRefCount<Object> o = (ObservableRefCount<Object>)new TestConnectableObservable<Object>().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorSubject<Integer> subject = BehaviorSubject.create(); + + Observable<Integer> observable = subject + .replay(1) + .refCount(); + + observable.takeUntil(Observable.just(1)).test(); + + subject.onNext(2); + + observable.take(1).test().assertResult(2); + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplaySubject<Integer> rs = ReplaySubject.create(); + rs.onNext(1); + rs.onComplete(); + + Observable<Integer> shared = rs.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java index 075f726283..538b4a0c69 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -30,6 +29,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -37,7 +37,7 @@ public class ObservableRepeatTest { @Test(timeout = 2000) public void testRepetition() { - int NUM = 10; + int num = 10; final AtomicInteger count = new AtomicInteger(); int value = Observable.unsafeCreate(new ObservableSource<Integer>() { @@ -47,9 +47,9 @@ public void subscribe(final Observer<? super Integer> o) { o.onComplete(); } }).repeat().subscribeOn(Schedulers.computation()) - .take(NUM).blockingLast(); + .take(num).blockingLast(); - assertEquals(NUM, value); + assertEquals(num, value); } @Test(timeout = 2000) @@ -162,20 +162,20 @@ public void testRepeatAndDistinctUnbounded() { .repeat(3) .distinct(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - src.subscribe(ts); + src.subscribe(to); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertValues(1, 2, 3); + to.assertNoErrors(); + to.assertTerminated(); + to.assertValues(1, 2, 3); } /** Issue #2844: wrong target of request. */ @Test(timeout = 3000) public void testRepeatRetarget() { final List<Integer> concatBase = new ArrayList<Integer>(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.just(1, 2) .repeat(5) .concatMap(new Function<Integer, Observable<Integer>>() { @@ -187,11 +187,11 @@ public Observable<Integer> apply(Integer x) { .delay(200, TimeUnit.MILLISECONDS); } }) - .subscribe(ts); + .subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - ts.assertNoValues(); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertNoValues(); assertEquals(Arrays.asList(1, 2, 1, 2, 1, 2, 1, 2, 1, 2), concatBase); } @@ -324,4 +324,129 @@ public Object apply(Object w) throws Exception { .test() .assertFailure(TestException.class, 1, 2, 3); } + + @Test + public void noCancelPreviousRepeat() { + final AtomicInteger counter = new AtomicInteger(); + + Observable<Integer> source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.repeat(5) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatUntil() { + final AtomicInteger counter = new AtomicInteger(); + + Observable<Integer> source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return times.getAndIncrement() == 4; + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + Observable<Integer> source = Observable.just(1).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + final AtomicInteger times = new AtomicInteger(); + + source.repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() { + @Override + public ObservableSource<?> apply(Observable<Object> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1, 1, 1, 1, 1); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + final PublishSubject<Integer> source = PublishSubject.create(); + final PublishSubject<Integer> signaller = PublishSubject.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestObserver<Integer> to = source.take(1) + .repeatWhen(new Function<Observable<Object>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Observable<Object> v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.onNext(1); + } + } + }; + + TestHelper.race(r1, r2); + + to.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java index d6f2a512c3..7c768f36cc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java @@ -14,12 +14,12 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -506,7 +506,6 @@ public void run() { } } - /* * test the basic expectation of OperatorMulticast via replay */ @@ -521,7 +520,7 @@ public void testIssue2191_UnsubscribeSource() throws Exception { Observer<Integer> spiedSubscriberAfterConnect = TestHelper.mockObserver(); // Observable under test - Observable<Integer> source = Observable.just(1,2); + Observable<Integer> source = Observable.just(1, 2); ConnectableObservable<Integer> replay = source .doOnNext(sourceNext) @@ -644,7 +643,6 @@ public void testIssue2191_SchedulerUnsubscribeOnError() throws Exception { verify(mockObserverBeforeConnect).onSubscribe((Disposable)any()); verify(mockObserverAfterConnect).onSubscribe((Disposable)any()); - mockScheduler.advanceTimeBy(1, TimeUnit.SECONDS); // verify interactions verify(sourceNext, times(1)).accept(1); @@ -681,7 +679,6 @@ public static Worker workerSpy(final Disposable mockDisposable) { return spy(new InprocessWorker(mockDisposable)); } - static class InprocessWorker extends Worker { private final Disposable mockDisposable; public boolean unsubscribed; @@ -872,13 +869,13 @@ public void testSizedTruncation() { public void testColdReplayNoBackpressure() { Observable<Integer> source = Observable.range(0, 1000).replay().autoConnect(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.subscribe(ts); + source.subscribe(to); - ts.assertNoErrors(); - ts.assertTerminated(); - List<Integer> onNextEvents = ts.values(); + to.assertNoErrors(); + to.assertTerminated(); + List<Integer> onNextEvents = to.values(); assertEquals(1000, onNextEvents.size()); for (int i = 0; i < 1000; i++) { @@ -941,23 +938,23 @@ public void accept(String v) { @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); - Observable<Integer> o = Observable.just(1).doOnDispose(unsubscribe).cache(); + Observable<Integer> o = Observable.just(1).doOnDispose(unsubscribe).replay().autoConnect(); o.subscribe(); o.subscribe(); o.subscribe(); - verify(unsubscribe, times(1)).run(); + verify(unsubscribe, never()).run(); } @Test public void testTake() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable<Integer> cached = Observable.range(1, 100).replay().autoConnect(); - cached.take(10).subscribe(ts); + cached.take(10).subscribe(to); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertNoErrors(); + to.assertTerminated(); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // FIXME no longer assertable // ts.assertUnsubscribed(); } @@ -966,26 +963,27 @@ public void testTake() { public void testAsync() { Observable<Integer> source = Observable.range(1, 10000); for (int i = 0; i < 100; i++) { - TestObserver<Integer> ts1 = new TestObserver<Integer>(); + TestObserver<Integer> to1 = new TestObserver<Integer>(); Observable<Integer> cached = source.replay().autoConnect(); - cached.observeOn(Schedulers.computation()).subscribe(ts1); + cached.observeOn(Schedulers.computation()).subscribe(to1); - ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts1.assertNoErrors(); - ts1.assertTerminated(); - assertEquals(10000, ts1.values().size()); + to1.awaitTerminalEvent(2, TimeUnit.SECONDS); + to1.assertNoErrors(); + to1.assertTerminated(); + assertEquals(10000, to1.values().size()); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); - cached.observeOn(Schedulers.computation()).subscribe(ts2); + TestObserver<Integer> to2 = new TestObserver<Integer>(); + cached.observeOn(Schedulers.computation()).subscribe(to2); - ts2.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts2.assertNoErrors(); - ts2.assertTerminated(); - assertEquals(10000, ts2.values().size()); + to2.awaitTerminalEvent(2, TimeUnit.SECONDS); + to2.assertNoErrors(); + to2.assertTerminated(); + assertEquals(10000, to2.values().size()); } } + @Test public void testAsyncComeAndGo() { Observable<Long> source = Observable.interval(1, 1, TimeUnit.MILLISECONDS) @@ -997,9 +995,9 @@ public void testAsyncComeAndGo() { List<TestObserver<Long>> list = new ArrayList<TestObserver<Long>>(100); for (int i = 0; i < 100; i++) { - TestObserver<Long> ts = new TestObserver<Long>(); - list.add(ts); - output.skip(i * 10).take(10).subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + list.add(to); + output.skip(i * 10).take(10).subscribe(to); } List<Long> expected = new ArrayList<Long>(); @@ -1007,16 +1005,16 @@ public void testAsyncComeAndGo() { expected.add((long)(i - 10)); } int j = 0; - for (TestObserver<Long> ts : list) { - ts.awaitTerminalEvent(3, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertTerminated(); + for (TestObserver<Long> to : list) { + to.awaitTerminalEvent(3, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertTerminated(); for (int i = j * 10; i < j * 10 + 10; i++) { expected.set(i - j * 10, (long)i); } - ts.assertValueSequence(expected); + to.assertValueSequence(expected); j++; } @@ -1036,14 +1034,14 @@ public void subscribe(Observer<? super Integer> t) { } }); - TestObserver<Integer> ts = new TestObserver<Integer>(); - firehose.replay().autoConnect().observeOn(Schedulers.computation()).takeLast(100).subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(); + firehose.replay().autoConnect().observeOn(Schedulers.computation()).takeLast(100).subscribe(to); - ts.awaitTerminalEvent(3, TimeUnit.SECONDS); - ts.assertNoErrors(); - ts.assertTerminated(); + to.awaitTerminalEvent(3, TimeUnit.SECONDS); + to.assertNoErrors(); + to.assertTerminated(); - assertEquals(100, ts.values().size()); + assertEquals(100, to.values().size()); } @Test @@ -1052,20 +1050,19 @@ public void testValuesAndThenError() { .concatWith(Observable.<Integer>error(new TestException())) .replay().autoConnect(); + TestObserver<Integer> to = new TestObserver<Integer>(); + source.subscribe(to); - TestObserver<Integer> ts = new TestObserver<Integer>(); - source.subscribe(ts); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertNotComplete(); + Assert.assertEquals(1, to.errors().size()); - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - ts.assertNotComplete(); - Assert.assertEquals(1, ts.errors().size()); + TestObserver<Integer> to2 = new TestObserver<Integer>(); + source.subscribe(to2); - TestObserver<Integer> ts2 = new TestObserver<Integer>(); - source.subscribe(ts2); - - ts2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - ts2.assertNotComplete(); - Assert.assertEquals(1, ts2.errors().size()); + to2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to2.assertNotComplete(); + Assert.assertEquals(1, to2.errors().size()); } @Test @@ -1082,20 +1079,20 @@ public void accept(Integer t) { }) .replay().autoConnect(); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { throw new TestException(); } }; - source.subscribe(ts); + source.subscribe(to); Assert.assertEquals(100, count.get()); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(TestException.class); } @Test @@ -1178,7 +1175,7 @@ public void source() { @Test public void connectRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.range(1, 3).replay(); Runnable r = new Runnable() { @@ -1194,7 +1191,7 @@ public void run() { @Test public void subscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.range(1, 3).replay(); final TestObserver<Integer> to1 = new TestObserver<Integer>(); @@ -1220,7 +1217,7 @@ public void run() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.range(1, 3).replay(); final TestObserver<Integer> to1 = new TestObserver<Integer>(); @@ -1318,7 +1315,7 @@ protected void subscribeActual(Observer<? super Integer> observer) { @Test public void subscribeOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final ConnectableObservable<Integer> co = ps.replay(); @@ -1347,7 +1344,7 @@ public void run() { @Test public void unsubscribeOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final ConnectableObservable<Integer> co = ps.replay(); @@ -1378,7 +1375,7 @@ public void run() { @Test public void unsubscribeReplayRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ConnectableObservable<Integer> co = Observable.range(1, 1000).replay(); final TestObserver<Integer> to1 = new TestObserver<Integer>(); @@ -1497,8 +1494,8 @@ public void delayedUpstreamOnSubscribe() { new Observable<Integer>() { @Override - protected void subscribeActual(Observer<? super Integer> s) { - sub[0] = s; + protected void subscribeActual(Observer<? super Integer> observer) { + sub[0] = observer; } } .replay() @@ -1561,4 +1558,221 @@ public void replaySelectorConnectableReturnsNull() { .test() .assertFailureAndMessage(NullPointerException.class, "The connectableFactory returned a null ConnectableObservable"); } -} + + @Test + public void noHeadRetentionCompleteSize() { + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionErrorSize() { + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionSize() { + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + + assertNotNull(buf.get().value); + + buf.trimHead(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionCompleteTime() { + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1, TimeUnit.MINUTES, Schedulers.computation()); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionErrorTime() { + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1, TimeUnit.MINUTES, Schedulers.computation()); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noHeadRetentionTime() { + TestScheduler sch = new TestScheduler(); + + PublishSubject<Integer> source = PublishSubject.create(); + + ObservableReplay<Integer> co = (ObservableReplay<Integer>)source + .replay(1, TimeUnit.MILLISECONDS, sch); + + co.connect(); + + BoundedReplayBuffer<Integer> buf = (BoundedReplayBuffer<Integer>)(co.current.get().buffer); + + source.onNext(1); + + sch.advanceTimeBy(2, TimeUnit.MILLISECONDS); + + source.onNext(2); + + assertNotNull(buf.get().value); + + buf.trimHead(); + + assertNull(buf.get().value); + + Object o = buf.get(); + + buf.trimHead(); + + assertSame(o, buf.get()); + } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + Observable<byte[]> source = Observable.range(1, 200) + .map(new Function<Integer, byte[]>() { + @Override + public byte[] apply(Integer v) throws Exception { + return new byte[1024 * 1024]; + } + }) + .replay(new Function<Observable<byte[]>, Observable<byte[]>>() { + @Override + public Observable<byte[]> apply(final Observable<byte[]> o) throws Exception { + return o.take(1) + .concatMap(new Function<byte[], Observable<byte[]>>() { + @Override + public Observable<byte[]> apply(byte[] v) throws Exception { + return o; + } + }); + } + }, 1) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer<byte[]>() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + }} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java index 4eaf547898..ab7998c8a1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java @@ -29,9 +29,11 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observables.GroupedObservable; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -60,7 +62,7 @@ public void subscribe(Observer<? super String> t1) { } }); - TestObserver<String> ts = new TestObserver<String>(consumer); + TestObserver<String> to = new TestObserver<String>(consumer); producer.retryWhen(new Function<Observable<? extends Throwable>, Observable<Object>>() { @Override @@ -86,9 +88,9 @@ public Observable<Long> apply(Tuple t) { Observable.timer(t.count * 1L, TimeUnit.MILLISECONDS); }}).cast(Object.class); } - }).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); InOrder inOrder = inOrder(consumer); inOrder.verify(consumer, never()).onError(any(Throwable.class)); @@ -111,13 +113,13 @@ public static class Tuple { @Test public void testRetryIndefinitely() { Observer<String> observer = TestHelper.mockObserver(); - int NUM_RETRIES = 20; - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 20; + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retry().subscribe(new TestObserver<String>(observer)); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -130,8 +132,8 @@ public void testRetryIndefinitely() { @Test public void testSchedulingNotificationHandler() { Observer<String> observer = TestHelper.mockObserver(); - int NUM_RETRIES = 2; - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); TestObserver<String> to = new TestObserver<String>(observer); origin.retryWhen(new Function<Observable<? extends Throwable>, Observable<Object>>() { @Override @@ -157,7 +159,7 @@ public void accept(Throwable e) { to.awaitTerminalEvent(); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -170,8 +172,8 @@ public void accept(Throwable e) { @Test public void testOnNextFromNotificationHandler() { Observer<String> observer = TestHelper.mockObserver(); - int NUM_RETRIES = 2; - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); + int numRetries = 2; + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numRetries)); origin.retryWhen(new Function<Observable<? extends Throwable>, Observable<Object>>() { @Override public Observable<Object> apply(Observable<? extends Throwable> t1) { @@ -187,7 +189,7 @@ public Integer apply(Throwable t1) { InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(NUM_RETRIES + 1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(numRetries + 1)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -284,15 +286,15 @@ public void testOriginFails() { @Test public void testRetryFail() { - int NUM_RETRIES = 1; - int NUM_FAILURES = 2; + int numRetries = 1; + int numFailures = 2; Observer<String> observer = TestHelper.mockObserver(); - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); - origin.retry(NUM_RETRIES).subscribe(observer); + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); + origin.retry(numRetries).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 2 attempts (first time fail, second time (1st retry) fail) - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numRetries)).onNext("beginningEveryTime"); // should only retry once, fail again and emit onError inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); // no success @@ -303,14 +305,14 @@ public void testRetryFail() { @Test public void testRetrySuccess() { - int NUM_FAILURES = 1; + int numFailures = 1; Observer<String> observer = TestHelper.mockObserver(); - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry(3).subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -322,14 +324,14 @@ public void testRetrySuccess() { @Test public void testInfiniteRetry() { - int NUM_FAILURES = 20; + int numFailures = 20; Observer<String> observer = TestHelper.mockObserver(); - Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); + Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(numFailures)); origin.retry().subscribe(observer); InOrder inOrder = inOrder(observer); // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1 + numFailures)).onNext("beginningEveryTime"); // should have no errors inOrder.verify(observer, never()).onError(any(Throwable.class)); // should have a single success @@ -426,9 +428,9 @@ public void testRetryAllowsSubscriptionAfterAllSubscriptionsUnsubscribed() throw final AtomicInteger subsCount = new AtomicInteger(0); ObservableSource<String> onSubscribe = new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { + public void subscribe(Observer<? super String> observer) { subsCount.incrementAndGet(); - s.onSubscribe(Disposables.fromRunnable(new Runnable() { + observer.onSubscribe(Disposables.fromRunnable(new Runnable() { @Override public void run() { subsCount.decrementAndGet(); @@ -451,17 +453,17 @@ public void run() { public void testSourceObservableCallsUnsubscribe() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); - final TestObserver<String> ts = new TestObserver<String>(); + final TestObserver<String> to = new TestObserver<String>(); ObservableSource<String> onSubscribe = new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { + public void subscribe(Observer<? super String> observer) { BooleanSubscription bs = new BooleanSubscription(); // if isUnsubscribed is true that means we have a bug such as // https://github.com/ReactiveX/RxJava/issues/1024 if (!bs.isCancelled()) { subsCount.incrementAndGet(); - s.onError(new RuntimeException("failed")); + observer.onError(new RuntimeException("failed")); // it unsubscribes the child directly // this simulates various error/completion scenarios that could occur // or just a source that proactively triggers cleanup @@ -469,12 +471,12 @@ public void subscribe(Observer<? super String> s) { // s.unsubscribe(); bs.cancel(); } else { - s.onError(new RuntimeException()); + observer.onError(new RuntimeException()); } } }; - Observable.unsafeCreate(onSubscribe).retry(3).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(3).subscribe(to); assertEquals(4, subsCount.get()); // 1 + 3 retries } @@ -482,18 +484,18 @@ public void subscribe(Observer<? super String> s) { public void testSourceObservableRetry1() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); - final TestObserver<String> ts = new TestObserver<String>(); + final TestObserver<String> to = new TestObserver<String>(); ObservableSource<String> onSubscribe = new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(Observer<? super String> observer) { + observer.onSubscribe(Disposables.empty()); subsCount.incrementAndGet(); - s.onError(new RuntimeException("failed")); + observer.onError(new RuntimeException("failed")); } }; - Observable.unsafeCreate(onSubscribe).retry(1).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(1).subscribe(to); assertEquals(2, subsCount.get()); } @@ -501,18 +503,18 @@ public void subscribe(Observer<? super String> s) { public void testSourceObservableRetry0() throws InterruptedException { final AtomicInteger subsCount = new AtomicInteger(0); - final TestObserver<String> ts = new TestObserver<String>(); + final TestObserver<String> to = new TestObserver<String>(); ObservableSource<String> onSubscribe = new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(Observer<? super String> observer) { + observer.onSubscribe(Disposables.empty()); subsCount.incrementAndGet(); - s.onError(new RuntimeException("failed")); + observer.onError(new RuntimeException("failed")); } }; - Observable.unsafeCreate(onSubscribe).retry(0).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(0).subscribe(to); assertEquals(1, subsCount.get()); } @@ -522,11 +524,14 @@ static final class SlowObservable implements ObservableSource<Long> { final AtomicInteger active = new AtomicInteger(0), maxActive = new AtomicInteger(0); final AtomicInteger nextBeforeFailure; + final String context; + private final int emitDelay; - SlowObservable(int emitDelay, int countNext) { + SlowObservable(int emitDelay, int countNext, String context) { this.emitDelay = emitDelay; this.nextBeforeFailure = new AtomicInteger(countNext); + this.context = context; } @Override @@ -542,7 +547,7 @@ public void run() { efforts.getAndIncrement(); active.getAndIncrement(); maxActive.set(Math.max(active.get(), maxActive.get())); - final Thread thread = new Thread() { + final Thread thread = new Thread(context) { @Override public void run() { long nr = 0; @@ -552,7 +557,9 @@ public void run() { if (nextBeforeFailure.getAndDecrement() > 0) { observer.onNext(nr++); } else { + active.decrementAndGet(); observer.onError(new RuntimeException("expected-failed")); + break; } } } catch (InterruptedException t) { @@ -613,7 +620,7 @@ public void testUnsubscribeAfterError() { Observer<Long> observer = TestHelper.mockObserver(); // Observable that always fails after 100ms - SlowObservable so = new SlowObservable(100, 0); + SlowObservable so = new SlowObservable(100, 0, "testUnsubscribeAfterError"); Observable<Long> o = Observable.unsafeCreate(so).retry(5); AsyncObserver<Long> async = new AsyncObserver<Long>(observer); @@ -634,11 +641,10 @@ public void testUnsubscribeAfterError() { @Test(timeout = 10000) public void testTimeoutWithRetry() { - @SuppressWarnings("unchecked") - DefaultObserver<Long> observer = mock(DefaultObserver.class); + Observer<Long> observer = TestHelper.mockObserver(); // Observable that sends every 100ms (timeout fails instead) - SlowObservable so = new SlowObservable(100, 10); + SlowObservable so = new SlowObservable(100, 10, "testTimeoutWithRetry"); Observable<Long> o = Observable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncObserver<Long> async = new AsyncObserver<Long>(observer); @@ -663,9 +669,9 @@ public void testRetryWithBackpressure() throws InterruptedException { for (int i = 0; i < 400; i++) { Observer<String> observer = TestHelper.mockObserver(); Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); - TestObserver<String> ts = new TestObserver<String>(observer); - origin.retry().observeOn(Schedulers.computation()).subscribe(ts); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); + TestObserver<String> to = new TestObserver<String>(observer); + origin.retry().observeOn(Schedulers.computation()).subscribe(to); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); InOrder inOrder = inOrder(observer); // should have no errors @@ -706,16 +712,16 @@ public void run() { final AtomicInteger nexts = new AtomicInteger(); try { Observable<String> origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); origin.retry() - .observeOn(Schedulers.computation()).subscribe(ts); - ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); - List<String> onNextEvents = new ArrayList<String>(ts.values()); + .observeOn(Schedulers.computation()).subscribe(to); + to.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); + List<String> onNextEvents = new ArrayList<String>(to.values()); if (onNextEvents.size() != NUM_RETRIES + 2) { - for (Throwable t : ts.errors()) { + for (Throwable t : to.errors()) { onNextEvents.add(t.toString()); } - for (long err = ts.completions(); err != 0; err--) { + for (long err = to.completions(); err != 0; err--) { onNextEvents.add("onComplete"); } data.put(j, onNextEvents); @@ -780,6 +786,7 @@ static <T> StringBuilder sequenceFrequency(Iterable<T> it) { return sb; } + @Test//(timeout = 3000) public void testIssue1900() throws InterruptedException { Observer<String> observer = TestHelper.mockObserver(); @@ -801,7 +808,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function<GroupedObservable<String,String>, Observable<String>>() { + .flatMap(new Function<GroupedObservable<String, String>, Observable<String>>() { @Override public Observable<String> apply(GroupedObservable<String, String> t1) { return t1.take(1); @@ -820,6 +827,7 @@ public Observable<String> apply(GroupedObservable<String, String> t1) { inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } + @Test//(timeout = 3000) public void testIssue1900SourceNotSupportingBackpressure() { Observer<String> observer = TestHelper.mockObserver(); @@ -845,7 +853,7 @@ public String apply(String t1) { return t1; } }) - .flatMap(new Function<GroupedObservable<String,String>, Observable<String>>() { + .flatMap(new Function<GroupedObservable<String, String>, Observable<String>>() { @Override public Observable<String> apply(GroupedObservable<String, String> t1) { return t1.take(1); @@ -930,4 +938,257 @@ public ObservableSource<Object> apply(Throwable ignore) throws Exception { assertFalse(subject.hasObservers()); } + @Test + public void noCancelPreviousRetry() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.defer(new Callable<ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.defer(new Callable<ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(5, Functions.alwaysTrue()) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryWhile2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.defer(new Callable<ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retry(new BiPredicate<Integer, Throwable>() { + @Override + public boolean test(Integer a, Throwable b) throws Exception { + return a < 5; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRetryUntil() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.defer(new Callable<ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> call() throws Exception { + if (times.getAndIncrement() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.defer(new Callable<ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> call() throws Exception { + if (times.get() < 4) { + return Observable.error(new TestException()); + } + return Observable.just(1); + } + }).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() { + @Override + public ObservableSource<?> apply(Observable<Throwable> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(1); + + assertEquals(0, counter.get()); + } + + @Test + public void noCancelPreviousRepeatWhen2() { + final AtomicInteger counter = new AtomicInteger(); + + final AtomicInteger times = new AtomicInteger(); + + Observable<Integer> source = Observable.<Integer>error(new TestException()).doOnDispose(new Action() { + @Override + public void run() throws Exception { + counter.getAndIncrement(); + } + }); + + source.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() { + @Override + public ObservableSource<?> apply(Observable<Throwable> e) throws Exception { + return e.takeWhile(new Predicate<Object>() { + @Override + public boolean test(Object v) throws Exception { + return times.getAndIncrement() < 4; + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, counter.get()); + } + + @Test + public void repeatFloodNoSubscriptionError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + final TestException error = new TestException(); + + try { + final PublishSubject<Integer> source = PublishSubject.create(); + final PublishSubject<Integer> signaller = PublishSubject.create(); + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + TestObserver<Integer> to = source.take(1) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw error; + } + }) + .retryWhen(new Function<Observable<Throwable>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Observable<Throwable> v) + throws Exception { + return signaller; + } + }).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + source.onNext(1); + } + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + signaller.onNext(1); + } + } + }; + + TestHelper.race(r1, r2); + + to.dispose(); + } + + if (!errors.isEmpty()) { + for (Throwable e : errors) { + e.printStackTrace(); + } + fail(errors + ""); + } + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java index 785de1a592..debeb8eec4 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -33,7 +32,6 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class ObservableRetryWithPredicateTest { @@ -70,6 +68,7 @@ public void testWithNothingToRetry() { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwice() { Observable<Integer> source = Observable.unsafeCreate(new ObservableSource<Integer>() { @@ -90,8 +89,7 @@ public void subscribe(Observer<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o = mock(DefaultObserver.class); + Observer<Integer> o = TestHelper.mockObserver(); InOrder inOrder = inOrder(o); source.retry(retryTwice).subscribe(o); @@ -106,6 +104,7 @@ public void subscribe(Observer<? super Integer> t1) { verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryTwiceAndGiveUp() { Observable<Integer> source = Observable.unsafeCreate(new ObservableSource<Integer>() { @@ -118,8 +117,7 @@ public void subscribe(Observer<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o = mock(DefaultObserver.class); + Observer<Integer> o = TestHelper.mockObserver(); InOrder inOrder = inOrder(o); source.retry(retryTwice).subscribe(o); @@ -134,6 +132,7 @@ public void subscribe(Observer<? super Integer> t1) { verify(o, never()).onComplete(); } + @Test public void testRetryOnSpecificException() { Observable<Integer> source = Observable.unsafeCreate(new ObservableSource<Integer>() { @@ -154,8 +153,7 @@ public void subscribe(Observer<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o = mock(DefaultObserver.class); + Observer<Integer> o = TestHelper.mockObserver(); InOrder inOrder = inOrder(o); source.retry(retryOnTestException).subscribe(o); @@ -169,6 +167,7 @@ public void subscribe(Observer<? super Integer> t1) { inOrder.verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } + @Test public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); @@ -191,8 +190,7 @@ public void subscribe(Observer<? super Integer> t1) { } }); - @SuppressWarnings("unchecked") - DefaultObserver<Integer> o = mock(DefaultObserver.class); + Observer<Integer> o = TestHelper.mockObserver(); InOrder inOrder = inOrder(o); source.retry(retryOnTestException).subscribe(o); @@ -230,7 +228,7 @@ public void testUnsubscribeAfterError() { Observer<Long> observer = TestHelper.mockObserver(); // Observable that always fails after 100ms - ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 0); + ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 0, "testUnsubscribeAfterError"); Observable<Long> o = Observable .unsafeCreate(so) .retry(retry5); @@ -256,7 +254,7 @@ public void testTimeoutWithRetry() { Observer<Long> observer = TestHelper.mockObserver(); // Observable that sends every 100ms (timeout fails instead) - ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 10); + ObservableRetryTest.SlowObservable so = new ObservableRetryTest.SlowObservable(100, 10, "testTimeoutWithRetry"); Observable<Long> o = Observable .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) @@ -278,7 +276,7 @@ public void testTimeoutWithRetry() { @Test public void testIssue2826() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final RuntimeException e = new RuntimeException("You shall not pass"); final AtomicInteger c = new AtomicInteger(); Observable.just(1).map(new Function<Integer, Integer>() { @@ -287,12 +285,13 @@ public Integer apply(Integer t1) { c.incrementAndGet(); throw e; } - }).retry(retry5).subscribe(ts); + }).retry(retry5).subscribe(to); - ts.assertTerminated(); + to.assertTerminated(); assertEquals(6, c.get()); - assertEquals(Collections.singletonList(e), ts.errors()); + assertEquals(Collections.singletonList(e), to.errors()); } + @Test public void testJustAndRetry() throws Exception { final AtomicBoolean throwException = new AtomicBoolean(true); @@ -334,7 +333,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -358,7 +357,7 @@ public void accept(Long t) { System.out.println(t); list.add(t); }}); - assertEquals(Arrays.asList(1L,1L,2L,3L), list); + assertEquals(Arrays.asList(1L, 1L, 2L, 3L), list); } @Test @@ -390,7 +389,7 @@ public void dontRetry() { @Test public void retryDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Integer> to = ps.retry(Functions.alwaysTrue()).test(); @@ -411,7 +410,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertEmpty(); } @@ -438,7 +437,7 @@ public boolean test(Integer n, Throwable e) throws Exception { @Test public void retryBiPredicateDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Integer> to = ps.retry(new BiPredicate<Object, Object>() { @@ -464,7 +463,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertEmpty(); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java index fb86ff92a8..7e3098216c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -24,6 +23,7 @@ import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.*; import io.reactivex.subjects.PublishSubject; @@ -265,17 +265,17 @@ public void sampleWithSamplerThrows() { @Test public void testSampleUnsubscribe() { - final Disposable s = mock(Disposable.class); + final Disposable upstream = mock(Disposable.class); Observable<Integer> o = Observable.unsafeCreate( new ObservableSource<Integer>() { @Override public void subscribe(Observer<? super Integer> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); } } ); o.throttleLast(1, TimeUnit.MILLISECONDS).subscribe().dispose(); - verify(s).dispose(); + verify(upstream).dispose(); } @Test @@ -319,20 +319,20 @@ public void emitLastTimedCustomScheduler() { @Test public void emitLastTimedRunCompleteRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestScheduler scheduler = new TestScheduler(); - final PublishSubject<Integer> pp = PublishSubject.create(); + final PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.sample(1, TimeUnit.SECONDS, scheduler, true) + TestObserver<Integer> to = ps.sample(1, TimeUnit.SECONDS, scheduler, true) .test(); - pp.onNext(1); + ps.onNext(1); Runnable r1 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; @@ -345,7 +345,7 @@ public void run() { TestHelper.race(r1, r2); - ts.assertResult(1); + to.assertResult(1); } } @@ -367,19 +367,19 @@ public void emitLastOtherEmpty() { @Test public void emitLastOtherRunCompleteRace() { - for (int i = 0; i < 1000; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); final PublishSubject<Integer> sampler = PublishSubject.create(); - TestObserver<Integer> ts = pp.sample(sampler, true) + TestObserver<Integer> to = ps.sample(sampler, true) .test(); - pp.onNext(1); + ps.onNext(1); Runnable r1 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; @@ -392,24 +392,24 @@ public void run() { TestHelper.race(r1, r2); - ts.assertResult(1); + to.assertResult(1); } } @Test public void emitLastOtherCompleteCompleteRace() { - for (int i = 0; i < 1000; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); final PublishSubject<Integer> sampler = PublishSubject.create(); - TestObserver<Integer> ts = pp.sample(sampler, true).test(); + TestObserver<Integer> to = ps.sample(sampler, true).test(); - pp.onNext(1); + ps.onNext(1); Runnable r1 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; @@ -422,8 +422,19 @@ public void run() { TestHelper.race(r1, r2); - ts.assertResult(1); + to.assertResult(1); } } + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> o) + throws Exception { + return o.sample(1, TimeUnit.SECONDS); + } + }); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableScalarXMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableScalarXMapTest.java index 587cc98fa9..0ab9e8e914 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableScalarXMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableScalarXMapTest.java @@ -25,7 +25,6 @@ import io.reactivex.internal.disposables.EmptyDisposable; import io.reactivex.internal.operators.observable.ObservableScalarXMap.ScalarDisposable; import io.reactivex.observers.TestObserver; -import io.reactivex.schedulers.Schedulers; public class ObservableScalarXMapTest { @@ -36,8 +35,8 @@ public void utilityClass() { static final class CallablePublisher implements ObservableSource<Integer>, Callable<Integer> { @Override - public void subscribe(Observer<? super Integer> s) { - EmptyDisposable.error(new TestException(), s); + public void subscribe(Observer<? super Integer> observer) { + EmptyDisposable.error(new TestException(), observer); } @Override @@ -48,8 +47,8 @@ public Integer call() throws Exception { static final class EmptyCallablePublisher implements ObservableSource<Integer>, Callable<Integer> { @Override - public void subscribe(Observer<? super Integer> s) { - EmptyDisposable.complete(s); + public void subscribe(Observer<? super Integer> observer) { + EmptyDisposable.complete(observer); } @Override @@ -60,9 +59,9 @@ public Integer call() throws Exception { static final class OneCallablePublisher implements ObservableSource<Integer>, Callable<Integer> { @Override - public void subscribe(Observer<? super Integer> s) { - ScalarDisposable<Integer> sd = new ScalarDisposable<Integer>(s, 1); - s.onSubscribe(sd); + public void subscribe(Observer<? super Integer> observer) { + ScalarDisposable<Integer> sd = new ScalarDisposable<Integer>(observer, 1); + observer.onSubscribe(sd); sd.run(); } @@ -74,85 +73,85 @@ public Integer call() throws Exception { @Test public void tryScalarXMap() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new CallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + TestObserver<Integer> to = new TestObserver<Integer>(); + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new CallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { return Observable.just(1); } })); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void emptyXMap() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new EmptyCallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new EmptyCallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { return Observable.just(1); } })); - ts.assertResult(); + to.assertResult(); } @Test public void mapperCrashes() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { throw new TestException(); } })); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void mapperToJust() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { return Observable.just(1); } })); - ts.assertResult(1); + to.assertResult(1); } @Test public void mapperToEmpty() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { return Observable.empty(); } })); - ts.assertResult(); + to.assertResult(); } @Test public void mapperToCrashingCallable() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), ts, new Function<Integer, ObservableSource<Integer>>() { + assertTrue(ObservableScalarXMap.tryScalarXMapSubscribe(new OneCallablePublisher(), to, new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Integer f) throws Exception { return new CallablePublisher(); } })); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -214,7 +213,7 @@ public void scalarDisposableStateCheck() { @Test public void scalarDisposableRunDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestObserver<Integer> to = new TestObserver<Integer>(); final ScalarDisposable<Integer> sd = new ScalarDisposable<Integer>(to, 1); to.onSubscribe(sd); @@ -233,7 +232,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java index 8baf9b427e..e078f92505 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -115,7 +114,7 @@ public Integer apply(Integer t1, Integer t2) { @Test public void shouldNotEmitUntilAfterSubscription() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, 100).scan(0, new BiFunction<Integer, Integer, Integer>() { @Override @@ -131,9 +130,9 @@ public boolean test(Integer t1) { return t1 > 0; } - }).subscribe(ts); + }).subscribe(to); - assertEquals(100, ts.values().size()); + assertEquals(100, to.values().size()); } @Test @@ -219,18 +218,18 @@ public Integer apply(Integer t1, Integer t2) { public void testInitialValueEmittedNoProducer() { PublishSubject<Integer> source = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); source.scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } - }).subscribe(ts); + }).subscribe(to); - ts.assertNoErrors(); - ts.assertNotComplete(); - ts.assertValue(0); + to.assertNoErrors(); + to.assertNotComplete(); + to.assertValue(0); } @Test @@ -326,7 +325,7 @@ public void subscribe(Observer<? super Integer> o) { o.onNext(2); o.onError(err2); }}) - .scan(new BiFunction<Integer,Integer,Integer>() { + .scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { throw err; @@ -351,7 +350,7 @@ public void subscribe(Observer<? super Integer> o) { o.onNext(2); o.onComplete(); }}) - .scan(new BiFunction<Integer,Integer,Integer>() { + .scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { throw err; @@ -374,7 +373,7 @@ public void subscribe(Observer<? super Integer> o) { o.onNext(2); o.onNext(3); }}) - .scan(new BiFunction<Integer,Integer,Integer>() { + .scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { count.incrementAndGet(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java index d4a38f78a4..da996cfc22 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import org.junit.*; @@ -150,9 +149,9 @@ private void verifyError(Observable<Boolean> observable) { inOrder.verifyNoMoreInteractions(); } - private void verifyError(Single<Boolean> observable) { + private void verifyError(Single<Boolean> single) { SingleObserver<Boolean> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onError(isA(TestException.class)); @@ -316,7 +315,7 @@ public void simpleInequalObservable() { @Test public void onNextCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Boolean> to = Observable.sequenceEqual(Observable.never(), ps).test(); @@ -343,7 +342,7 @@ public void run() { @Test public void onNextCancelRaceObservable() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Boolean> to = Observable.sequenceEqual(Observable.never(), ps).toObservable().test(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSingleTest.java index a0a6b841cc..9407cf4969 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSingleTest.java @@ -282,6 +282,7 @@ public void testSingleWithEmpty() { InOrder inOrder = inOrder(observer); inOrder.verify(observer).onComplete(); + inOrder.verify(observer, never()).onError(any(Throwable.class)); inOrder.verifyNoMoreInteractions(); } @@ -555,4 +556,13 @@ public MaybeSource<Object> apply(Observable<Object> o) throws Exception { } }); } + + @Test + public void singleOrError() { + Observable.empty() + .singleOrError() + .toObservable() + .test() + .assertFailure(NoSuchElementException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java index 73484edcf6..c97f851700 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.Arrays; @@ -24,6 +23,7 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.Schedulers; @@ -95,11 +95,11 @@ public void testSkipLastWithNull() { @Test public void testSkipLastWithBackpressure() { Observable<Integer> o = Observable.range(0, Flowable.bufferSize() * 2).skipLast(Flowable.bufferSize() + 10); - TestObserver<Integer> ts = new TestObserver<Integer>(); - o.observeOn(Schedulers.computation()).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals((Flowable.bufferSize()) - 10, ts.valueCount()); + TestObserver<Integer> to = new TestObserver<Integer>(); + o.observeOn(Schedulers.computation()).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals((Flowable.bufferSize()) - 10, to.valueCount()); } @@ -120,4 +120,15 @@ public void error() { .test() .assertFailure(TestException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> o) + throws Exception { + return o.skipLast(1); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java index 7e73421ede..3cf4d2e155 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -195,7 +194,7 @@ public ObservableSource<Object> apply(Observable<Object> o) throws Exception { @Test public void onNextDisposeRace() { TestScheduler scheduler = new TestScheduler(); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Integer> to = ps.skipLast(1, TimeUnit.DAYS, scheduler).test(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java index 6b54ad0b3e..9b1de8ab4e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; public class ObservableSkipTest { @@ -136,16 +137,27 @@ public void testSkipError() { @Test public void testRequestOverflowDoesNotOccur() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.range(1, 10).skip(5).subscribe(ts); - ts.assertTerminated(); - ts.assertComplete(); - ts.assertNoErrors(); - assertEquals(Arrays.asList(6,7,8,9,10), ts.values()); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.range(1, 10).skip(5).subscribe(to); + to.assertTerminated(); + to.assertComplete(); + to.assertNoErrors(); + assertEquals(Arrays.asList(6, 7, 8, 9, 10), to.values()); } @Test public void dispose() { TestHelper.checkDisposed(Observable.just(1).skip(2)); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> o) + throws Exception { + return o.skip(1); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java index d4772b1a6a..9e809b88cc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSubscribeOnTest.java index 6418c5338f..c80b81e2cd 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSubscribeOnTest.java @@ -74,33 +74,33 @@ public void subscribe( @Test @Ignore("ObservableSource.subscribe can't throw") public void testThrownErrorHandling() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { + public void subscribe(Observer<? super String> observer) { throw new RuntimeException("fail"); } - }).subscribeOn(Schedulers.computation()).subscribe(ts); - ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); - ts.assertTerminated(); + }).subscribeOn(Schedulers.computation()).subscribe(to); + to.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + to.assertTerminated(); } @Test public void testOnError() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { - s.onSubscribe(Disposables.empty()); - s.onError(new RuntimeException("fail")); + public void subscribe(Observer<? super String> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new RuntimeException("fail")); } - }).subscribeOn(Schedulers.computation()).subscribe(ts); - ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); - ts.assertTerminated(); + }).subscribeOn(Schedulers.computation()).subscribe(to); + to.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + to.assertTerminated(); } public static class SlowScheduler extends Scheduler { @@ -162,7 +162,7 @@ public Disposable schedule(@NonNull final Runnable action, final long delayTime, @Test(timeout = 5000) public void testUnsubscribeInfiniteStream() throws InterruptedException { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger count = new AtomicInteger(); Observable.unsafeCreate(new ObservableSource<Integer>() { @@ -176,12 +176,12 @@ public void subscribe(Observer<? super Integer> sub) { } } - }).subscribeOn(Schedulers.newThread()).take(10).subscribe(ts); + }).subscribeOn(Schedulers.newThread()).take(10).subscribe(to); - ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); - ts.dispose(); + to.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS); + to.dispose(); Thread.sleep(200); // give time for the loop to continue - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); assertEquals(10, count.get()); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java index 482c6f84d6..83024d1ea5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmptyTest.java @@ -25,7 +25,6 @@ import io.reactivex.functions.Consumer; import io.reactivex.observers.DefaultObserver; - public class ObservableSwitchIfEmptyTest { @Test @@ -35,7 +34,7 @@ public void testSwitchWhenNotEmpty() throws Exception { .switchIfEmpty(Observable.just(2) .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { + public void accept(Disposable d) { subscribed.set(true); } })); @@ -90,7 +89,6 @@ public void onNext(Long aLong) { } }).subscribe(); - assertTrue(d.isDisposed()); // FIXME no longer assertable // assertTrue(sub.isUnsubscribed()); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java index cb6587f589..83b5a51270 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java @@ -14,25 +14,28 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.util.ExceptionHelper; -import io.reactivex.observers.TestObserver; +import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.TestScheduler; +import io.reactivex.schedulers.*; import io.reactivex.subjects.PublishSubject; public class ObservableSwitchTest { @@ -461,7 +464,7 @@ public String apply(Long i) { .share() ; - TestObserver<String> ts = new TestObserver<String>() { + TestObserver<String> to = new TestObserver<String>() { @Override public void onNext(String t) { super.onNext(t); @@ -471,27 +474,26 @@ public void onNext(String t) { } } }; - src.subscribe(ts); + src.subscribe(to); - ts.awaitTerminalEvent(10, TimeUnit.SECONDS); + to.awaitTerminalEvent(10, TimeUnit.SECONDS); - System.out.println("> testIssue2654: " + ts.valueCount()); + System.out.println("> testIssue2654: " + to.valueCount()); - ts.assertTerminated(); - ts.assertNoErrors(); + to.assertTerminated(); + to.assertNoErrors(); - Assert.assertEquals(250, ts.valueCount()); + Assert.assertEquals(250, to.valueCount()); } - @Test public void delayErrors() { PublishSubject<ObservableSource<Integer>> source = PublishSubject.create(); - TestObserver<Integer> ts = source.switchMapDelayError(Functions.<ObservableSource<Integer>>identity()) + TestObserver<Integer> to = source.switchMapDelayError(Functions.<ObservableSource<Integer>>identity()) .test(); - ts.assertNoValues() + to.assertNoValues() .assertNoErrors() .assertNotComplete(); @@ -507,11 +509,11 @@ public void delayErrors() { source.onError(new TestException("Forced failure 3")); - ts.assertValues(1, 2, 3, 4, 5) + to.assertValues(1, 2, 3, 4, 5) .assertNotComplete() .assertError(CompositeException.class); - List<Throwable> errors = ExceptionHelper.flatten(ts.errors().get(0)); + List<Throwable> errors = ExceptionHelper.flatten(to.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class, "Forced failure 1"); TestHelper.assertError(errors, 1, TestException.class, "Forced failure 2"); @@ -522,40 +524,40 @@ public void delayErrors() { public void switchOnNextDelayError() { PublishSubject<Observable<Integer>> ps = PublishSubject.create(); - TestObserver<Integer> ts = Observable.switchOnNextDelayError(ps).test(); + TestObserver<Integer> to = Observable.switchOnNextDelayError(ps).test(); ps.onNext(Observable.just(1)); ps.onNext(Observable.range(2, 4)); ps.onComplete(); - ts.assertResult(1, 2, 3, 4, 5); + to.assertResult(1, 2, 3, 4, 5); } @Test public void switchOnNextDelayErrorWithError() { PublishSubject<Observable<Integer>> ps = PublishSubject.create(); - TestObserver<Integer> ts = Observable.switchOnNextDelayError(ps).test(); + TestObserver<Integer> to = Observable.switchOnNextDelayError(ps).test(); ps.onNext(Observable.just(1)); ps.onNext(Observable.<Integer>error(new TestException())); ps.onNext(Observable.range(2, 4)); ps.onComplete(); - ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); + to.assertFailure(TestException.class, 1, 2, 3, 4, 5); } @Test public void switchOnNextDelayErrorBufferSize() { PublishSubject<Observable<Integer>> ps = PublishSubject.create(); - TestObserver<Integer> ts = Observable.switchOnNextDelayError(ps, 2).test(); + TestObserver<Integer> to = Observable.switchOnNextDelayError(ps, 2).test(); ps.onNext(Observable.just(1)); ps.onNext(Observable.range(2, 4)); ps.onComplete(); - ts.assertResult(1, 2, 3, 4, 5); + to.assertResult(1, 2, 3, 4, 5); } @Test @@ -607,20 +609,19 @@ public ObservableSource<Integer> apply(Object v) throws Exception { } - @Test public void switchMapInnerCancelled() { - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = Observable.just(1) - .switchMap(Functions.justFunction(pp)) + TestObserver<Integer> to = Observable.just(1) + .switchMap(Functions.justFunction(ps)) .test(); - assertTrue(pp.hasObservers()); + assertTrue(ps.hasObservers()); - ts.cancel(); + to.cancel(); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); } @Test @@ -663,9 +664,9 @@ public void switchMapSingleFunctionDoesntReturnSingle() { public SingleSource<Integer> apply(Object v) throws Exception { return new SingleSource<Integer>() { @Override - public void subscribe(SingleObserver<? super Integer> s) { - s.onSubscribe(Disposables.empty()); - s.onSuccess(1); + public void subscribe(SingleObserver<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSuccess(1); } }; } @@ -721,7 +722,7 @@ public void dispose() { @Test public void nextSourceErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -768,7 +769,7 @@ public void run() { @Test public void outerInnerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -786,6 +787,8 @@ public ObservableSource<Integer> apply(Integer v) throws Exception { }) .test(); + ps1.onNext(1); + final TestException ex1 = new TestException(); Runnable r1 = new Runnable() { @@ -807,7 +810,7 @@ public void run() { TestHelper.race(r1, r2); for (Throwable e : errors) { - assertTrue(e.toString(), e instanceof TestException); + assertTrue(e.getCause().toString(), e.getCause() instanceof TestException); } } finally { RxJavaPlugins.reset(); @@ -817,7 +820,7 @@ public void run() { @Test public void nextCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps1 = PublishSubject.create(); final TestObserver<Integer> to = ps1.switchMap(new Function<Integer, ObservableSource<Integer>>() { @@ -963,4 +966,330 @@ public void onNext(Integer t) { to.assertFailure(TestException.class, 1); } + + @Test + public void innerDisposedOnMainError() { + final PublishSubject<Integer> main = PublishSubject.create(); + final PublishSubject<Integer> inner = PublishSubject.create(); + + TestObserver<Integer> to = main.switchMap(Functions.justFunction(inner)) + .test(); + + assertTrue(main.hasObservers()); + + main.onNext(1); + + assertTrue(inner.hasObservers()); + + main.onError(new TestException()); + + assertFalse(inner.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void outerInnerErrorRaceIgnoreDispose() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + final AtomicReference<Observer<? super Integer>> obs1 = new AtomicReference<Observer<? super Integer>>(); + final Observable<Integer> ps1 = new Observable<Integer>() { + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + obs1.set(observer); + } + }; + final AtomicReference<Observer<? super Integer>> obs2 = new AtomicReference<Observer<? super Integer>>(); + final Observable<Integer> ps2 = new Observable<Integer>() { + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + obs2.set(observer); + } + }; + + ps1.switchMap(new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer v) throws Exception { + if (v == 1) { + return ps2; + } + return Observable.never(); + } + }) + .test(); + + obs1.get().onSubscribe(Disposables.empty()); + obs1.get().onNext(1); + + obs2.get().onSubscribe(Disposables.empty()); + + final TestException ex1 = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + obs1.get().onError(ex1); + } + }; + + final TestException ex2 = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + obs2.get().onError(ex2); + } + }; + + TestHelper.race(r1, r2); + + for (Throwable e : errors) { + assertTrue(e.toString(), e.getCause() instanceof TestException); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void asyncFused() { + Observable.just(1).hide() + .switchMap(Functions.justFunction( + Observable.range(1, 5) + .observeOn(ImmediateThinScheduler.INSTANCE) + )) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void syncFusedMaybe() { + Observable.range(1, 5).hide() + .switchMap(Functions.justFunction( + Maybe.just(1).toObservable() + )) + .test() + .assertResult(1, 1, 1, 1, 1); + } + + @Test + public void syncFusedSingle() { + Observable.range(1, 5).hide() + .switchMap(Functions.justFunction( + Single.just(1).toObservable() + )) + .test() + .assertResult(1, 1, 1, 1, 1); + } + + @Test + public void syncFusedCompletable() { + Observable.range(1, 5).hide() + .switchMap(Functions.justFunction( + Completable.complete().toObservable() + )) + .test() + .assertResult(); + } + + @Test + public void asyncFusedRejecting() { + Observable.just(1).hide() + .switchMap(Functions.justFunction( + TestHelper.rejectObservableFusion() + )) + .test() + .assertEmpty(); + } + + @Test + public void asyncFusedPollCrash() { + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps + .switchMap(Functions.justFunction( + Observable.range(1, 5) + .observeOn(ImmediateThinScheduler.INSTANCE) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.<Integer>observableStripBoundary()) + )) + .test(); + + to.assertEmpty(); + + ps.onNext(1); + + to + .assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void asyncFusedPollCrashDelayError() { + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps + .switchMapDelayError(Functions.justFunction( + Observable.range(1, 5) + .observeOn(ImmediateThinScheduler.INSTANCE) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.<Integer>observableStripBoundary()) + )) + .test(); + + to.assertEmpty(); + + ps.onNext(1); + + assertTrue(ps.hasObservers()); + + to.assertEmpty(); + + ps.onComplete(); + + to + .assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void fusedBoundary() { + String thread = Thread.currentThread().getName(); + + Observable.range(1, 10000) + .switchMap(new Function<Integer, ObservableSource<? extends Object>>() { + @Override + public ObservableSource<? extends Object> apply(Integer v) + throws Exception { + return Observable.just(2).hide() + .observeOn(Schedulers.single()) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer w) throws Exception { + return Thread.currentThread().getName(); + } + }); + } + }) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertNever(thread) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void switchMapFusedIterable() { + Observable.range(1, 2) + .switchMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.fromIterable(Arrays.asList(v * 10)); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void switchMapHiddenIterable() { + Observable.range(1, 2) + .switchMap(new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer v) + throws Exception { + return Observable.fromIterable(Arrays.asList(v * 10)).hide(); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void cancellationShouldTriggerInnerCancellationRace() throws Throwable { + final AtomicInteger outer = new AtomicInteger(); + final AtomicInteger inner = new AtomicInteger(); + + int n = 10000; + for (int i = 0; i < n; i++) { + Observable.<Integer>create(new ObservableOnSubscribe<Integer>() { + @Override + public void subscribe(ObservableEmitter<Integer> it) + throws Exception { + it.onNext(0); + } + }) + .switchMap(new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer v) + throws Exception { + return createObservable(inner); + } + }) + .observeOn(Schedulers.computation()) + .doFinally(new Action() { + @Override + public void run() throws Exception { + outer.incrementAndGet(); + } + }) + .take(1) + .blockingSubscribe(Functions.emptyConsumer(), new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + e.printStackTrace(); + } + }); + } + + Thread.sleep(100); + assertEquals(inner.get(), outer.get()); + assertEquals(n, inner.get()); + } + + Observable<Integer> createObservable(final AtomicInteger inner) { + return Observable.<Integer>unsafeCreate(new ObservableSource<Integer>() { + @Override + public void subscribe(Observer<? super Integer> observer) { + final SerializedObserver<Integer> it = new SerializedObserver<Integer>(observer); + it.onSubscribe(Disposables.empty()); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(1); + } + }, 0, TimeUnit.MILLISECONDS); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(2); + } + }, 0, TimeUnit.MILLISECONDS); + } + }) + .doFinally(new Action() { + @Override + public void run() throws Exception { + inner.incrementAndGet(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java index 15daa252e8..7dc3a1cf39 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastOneTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import io.reactivex.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; @@ -27,33 +28,33 @@ public class ObservableTakeLastOneTest { @Test public void testLastOfManyReturnsLast() { - TestObserver<Integer> s = new TestObserver<Integer>(); - Observable.range(1, 10).takeLast(1).subscribe(s); - s.assertValue(10); - s.assertNoErrors(); - s.assertTerminated(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.range(1, 10).takeLast(1).subscribe(to); + to.assertValue(10); + to.assertNoErrors(); + to.assertTerminated(); // NO longer assertable // s.assertUnsubscribed(); } @Test public void testLastOfEmptyReturnsEmpty() { - TestObserver<Object> s = new TestObserver<Object>(); - Observable.empty().takeLast(1).subscribe(s); - s.assertNoValues(); - s.assertNoErrors(); - s.assertTerminated(); + TestObserver<Object> to = new TestObserver<Object>(); + Observable.empty().takeLast(1).subscribe(to); + to.assertNoValues(); + to.assertNoErrors(); + to.assertTerminated(); // NO longer assertable // s.assertUnsubscribed(); } @Test public void testLastOfOneReturnsLast() { - TestObserver<Integer> s = new TestObserver<Integer>(); - Observable.just(1).takeLast(1).subscribe(s); - s.assertValue(1); - s.assertNoErrors(); - s.assertTerminated(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).takeLast(1).subscribe(to); + to.assertValue(1); + to.assertNoErrors(); + to.assertTerminated(); // NO longer assertable // s.assertUnsubscribed(); } @@ -81,7 +82,7 @@ public void run() { public void testTakeLastZeroProcessesAllItemsButIgnoresThem() { final AtomicInteger upstreamCount = new AtomicInteger(); final int num = 10; - long count = Observable.range(1,num).doOnNext(new Consumer<Integer>() { + long count = Observable.range(1, num).doOnNext(new Consumer<Integer>() { @Override public void accept(Integer t) { @@ -106,4 +107,12 @@ public ObservableSource<Object> apply(Observable<Object> f) throws Exception { } }); } + + @Test + public void error() { + Observable.error(new TestException()) + .takeLast(1) + .test() + .assertFailure(TestException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java index e2c91fb6a4..1eb89adc6a 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.atomic.AtomicInteger; @@ -103,23 +102,23 @@ public void testTakeLastWithNegativeCount() { @Test public void testBackpressure1() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, 100000).takeLast(1) .observeOn(Schedulers.newThread()) - .map(newSlowProcessor()).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - ts.assertValue(100000); + .map(newSlowProcessor()).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertValue(100000); } @Test public void testBackpressure2() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Observable.range(1, 100000).takeLast(Flowable.bufferSize() * 4) - .observeOn(Schedulers.newThread()).map(newSlowProcessor()).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Flowable.bufferSize() * 4, ts.valueCount()); + .observeOn(Schedulers.newThread()).map(newSlowProcessor()).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, to.valueCount()); } private Function<Integer, Integer> newSlowProcessor() { @@ -179,7 +178,7 @@ public void onNext(Integer integer) { cancel(); } }); - assertEquals(1,count.get()); + assertEquals(1, count.get()); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java index 75e8948284..2aa3f38c7e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -254,7 +253,7 @@ public void observeOn() { @Test public void cancelCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestObserver<Integer> to = ps.takeLast(1, TimeUnit.DAYS).test(); @@ -276,4 +275,27 @@ public void run() { TestHelper.race(r1, r2); } } + + @Test + public void lastWindowIsFixedInTime() { + TimesteppingScheduler scheduler = new TimesteppingScheduler(); + scheduler.stepEnabled = false; + + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps + .takeLast(2, TimeUnit.SECONDS, scheduler) + .test(); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + + scheduler.stepEnabled = true; + + ps.onComplete(); + + to.assertResult(1, 2, 3, 4); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java index c094ad0c06..341409f958 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java @@ -16,7 +16,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.*; @@ -24,10 +24,13 @@ import org.mockito.InOrder; import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -204,13 +207,13 @@ public void testMultiTake() { Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s) { + public void subscribe(Observer<? super Integer> observer) { Disposable bs = Disposables.empty(); - s.onSubscribe(bs); + observer.onSubscribe(bs); for (int i = 0; !bs.isDisposed(); i++) { System.out.println("Emit: " + i); count.incrementAndGet(); - s.onNext(i); + observer.onNext(i); } } @@ -281,12 +284,12 @@ public void subscribe(Observer<? super Long> op) { @Test(timeout = 2000) public void testTakeObserveOn() { Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); INFINITE_OBSERVABLE - .observeOn(Schedulers.newThread()).take(1).subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + .observeOn(Schedulers.newThread()).take(1).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); verify(o).onNext(1L); verify(o, never()).onNext(2L); @@ -323,38 +326,38 @@ public void accept(Integer t1) { public void takeFinalValueThrows() { Observable<Integer> source = Observable.just(1).take(1); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { throw new TestException(); } }; - source.safeSubscribe(ts); + source.safeSubscribe(to); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test public void testReentrantTake() { final PublishSubject<Integer> source = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); source.take(1).doOnNext(new Consumer<Integer>() { @Override public void accept(Integer v) { source.onNext(2); } - }).subscribe(ts); + }).subscribe(to); source.onNext(1); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(1); + to.assertNoErrors(); + to.assertComplete(); } @Test @@ -389,4 +392,19 @@ public ObservableSource<Object> apply(Observable<Object> o) throws Exception { } }); } + + @Test + public void errorAfterLimitReached() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + Observable.error(new TestException()) + .take(0) + .test() + .assertResult(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java index 6e3614525b..9fa6c21bca 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -46,6 +45,7 @@ public boolean test(Object v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeAll() { Observer<Object> o = TestHelper.mockObserver(); @@ -62,6 +62,7 @@ public boolean test(Integer v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeFirst() { Observer<Object> o = TestHelper.mockObserver(); @@ -78,6 +79,7 @@ public boolean test(Integer v) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void takeSome() { Observer<Object> o = TestHelper.mockObserver(); @@ -96,6 +98,7 @@ public boolean test(Integer t1) { verify(o, never()).onError(any(Throwable.class)); verify(o).onComplete(); } + @Test public void functionThrows() { Observer<Object> o = TestHelper.mockObserver(); @@ -114,6 +117,7 @@ public boolean test(Integer t1) { verify(o).onError(any(TestException.class)); verify(o, never()).onComplete(); } + @Test public void sourceThrows() { Observer<Object> o = TestHelper.mockObserver(); @@ -136,7 +140,7 @@ public boolean test(Integer v) { @Test public void testErrorIncludesLastValueAsCause() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); final TestException e = new TestException("Forced failure"); Predicate<String> predicate = (new Predicate<String>() { @Override @@ -144,11 +148,11 @@ public boolean test(String t) { throw e; } }); - Observable.just("abc").takeUntil(predicate).subscribe(ts); + Observable.just("abc").takeUntil(predicate).subscribe(to); - ts.assertTerminated(); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertTerminated(); + to.assertNotComplete(); + to.assertError(TestException.class); // FIXME last cause value is not saved // assertTrue(ts.errors().get(0).getCause().getMessage().contains("abc")); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java index 4a23b3dd55..7ea5698be3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilTest.java @@ -20,6 +20,8 @@ import io.reactivex.*; import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.observers.TestObserver; import io.reactivex.subjects.PublishSubject; @@ -68,7 +70,7 @@ public void testTakeUntilSourceCompleted() { verify(result, times(1)).onNext("one"); verify(result, times(1)).onNext("two"); - verify(sSource, times(1)).dispose(); + verify(sSource, never()).dispose(); // no longer disposing itself on terminal events verify(sOther, times(1)).dispose(); } @@ -93,7 +95,7 @@ public void testTakeUntilSourceError() { verify(result, times(1)).onNext("two"); verify(result, times(0)).onNext("three"); verify(result, times(1)).onError(error); - verify(sSource, times(1)).dispose(); + verify(sSource, never()).dispose(); // no longer disposing itself on terminal events verify(sOther, times(1)).dispose(); } @@ -120,7 +122,7 @@ public void testTakeUntilOtherError() { verify(result, times(1)).onError(error); verify(result, times(0)).onComplete(); verify(sSource, times(1)).dispose(); - verify(sOther, times(1)).dispose(); + verify(sOther, never()).dispose(); // no longer disposing itself on termination } @@ -147,17 +149,17 @@ public void testTakeUntilOtherCompleted() { verify(result, times(0)).onNext("three"); verify(result, times(1)).onComplete(); verify(sSource, times(1)).dispose(); - verify(sOther, times(1)).dispose(); // unsubscribed since SafeSubscriber unsubscribes after onComplete + verify(sOther, never()).dispose(); // no longer disposing itself on terminal events } private static class TestObservable implements ObservableSource<String> { Observer<? super String> observer; - Disposable s; + Disposable upstream; - TestObservable(Disposable s) { - this.s = s; + TestObservable(Disposable d) { + this.upstream = d; } /* used to simulate subscription */ @@ -178,7 +180,7 @@ public void sendOnError(Throwable e) { @Override public void subscribe(Observer<? super String> observer) { this.observer = observer; - observer.onSubscribe(s); + observer.onSubscribe(upstream); } } @@ -187,35 +189,36 @@ public void testUntilFires() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> until = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.takeUntil(until).subscribe(ts); + source.takeUntil(until).subscribe(to); assertTrue(source.hasObservers()); assertTrue(until.hasObservers()); source.onNext(1); - ts.assertValue(1); + to.assertValue(1); until.onNext(1); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertTerminated(); + to.assertValue(1); + to.assertNoErrors(); + to.assertTerminated(); assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); } + @Test public void testMainCompletes() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> until = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.takeUntil(until).subscribe(ts); + source.takeUntil(until).subscribe(to); assertTrue(source.hasObservers()); assertTrue(until.hasObservers()); @@ -223,32 +226,33 @@ public void testMainCompletes() { source.onNext(1); source.onComplete(); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertTerminated(); + to.assertValue(1); + to.assertNoErrors(); + to.assertTerminated(); assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); } + @Test public void testDownstreamUnsubscribes() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> until = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.takeUntil(until).take(1).subscribe(ts); + source.takeUntil(until).take(1).subscribe(to); assertTrue(source.hasObservers()); assertTrue(until.hasObservers()); source.onNext(1); - ts.assertValue(1); - ts.assertNoErrors(); - ts.assertTerminated(); + to.assertValue(1); + to.assertNoErrors(); + to.assertTerminated(); assertFalse("Source still has observers", source.hasObservers()); assertFalse("Until still has observers", until.hasObservers()); @@ -260,4 +264,143 @@ public void testDownstreamUnsubscribes() { public void dispose() { TestHelper.checkDisposed(PublishSubject.create().takeUntil(Observable.never())); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Integer>, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Observable<Integer> o) throws Exception { + return o.takeUntil(Observable.never()); + } + }); + } + + @Test + public void untilPublisherMainSuccess() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onNext(1); + main.onNext(2); + main.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(1, 2); + } + + @Test + public void untilPublisherMainComplete() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilPublisherMainError() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherOtherOnNext() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onNext(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilPublisherOtherOnComplete() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(); + } + + @Test + public void untilPublisherOtherError() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherDispose() { + PublishSubject<Integer> main = PublishSubject.create(); + PublishSubject<Integer> other = PublishSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertEmpty(); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeWhileTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeWhileTest.java index 545cb4e65e..6a92491a87 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeWhileTest.java @@ -145,8 +145,8 @@ public boolean test(String s) { @Test public void testUnsubscribeAfterTake() { - Disposable s = mock(Disposable.class); - TestObservable w = new TestObservable(s, "one", "two", "three"); + Disposable upstream = mock(Disposable.class); + TestObservable w = new TestObservable(upstream, "one", "two", "three"); Observer<String> observer = TestHelper.mockObserver(); Observable<String> take = Observable.unsafeCreate(w) @@ -172,24 +172,24 @@ public boolean test(String s) { verify(observer, times(1)).onNext("one"); verify(observer, never()).onNext("two"); verify(observer, never()).onNext("three"); - verify(s, times(1)).dispose(); + verify(upstream, times(1)).dispose(); } private static class TestObservable implements ObservableSource<String> { - final Disposable s; + final Disposable upstream; final String[] values; Thread t; - TestObservable(Disposable s, String... values) { - this.s = s; + TestObservable(Disposable upstream, String... values) { + this.upstream = upstream; this.values = values; } @Override public void subscribe(final Observer<? super String> observer) { System.out.println("TestObservable subscribed to ..."); - observer.onSubscribe(s); + observer.onSubscribe(upstream); t = new Thread(new Runnable() { @Override @@ -221,12 +221,12 @@ public boolean test(Integer t1) { return t1 < 2; } }); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - source.subscribe(ts); + source.subscribe(to); - ts.assertNoErrors(); - ts.assertValue(1); + to.assertNoErrors(); + to.assertValue(1); // 2.0.2 - not anymore // Assert.assertTrue("Not cancelled!", ts.isCancelled()); @@ -234,17 +234,17 @@ public boolean test(Integer t1) { @Test public void testErrorCauseIncludesLastValue() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.just("abc").takeWhile(new Predicate<String>() { @Override public boolean test(String t1) { throw new TestException(); } - }).subscribe(ts); + }).subscribe(to); - ts.assertTerminated(); - ts.assertNoValues(); - ts.assertError(TestException.class); + to.assertTerminated(); + to.assertNoValues(); + to.assertError(TestException.class); // FIXME last cause value not recorded // assertTrue(ts.getOnErrorEvents().get(0).getCause().getMessage().contains("abc")); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java new file mode 100644 index 0000000000..059d516da1 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableThrottleLatestTest.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.observable; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subjects.PublishSubject; + +public class ObservableThrottleLatestTest { + + @Test + public void just() { + Observable.just(1) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertResult(1); + } + + @Test + public void range() { + Observable.range(1, 5) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertResult(1); + } + + @Test + public void rangeEmitLatest() { + Observable.range(1, 5) + .throttleLatest(1, TimeUnit.MINUTES, true) + .test() + .assertResult(1, 5); + } + + @Test + public void error() { + Observable.error(new TestException()) + .throttleLatest(1, TimeUnit.MINUTES) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Object>>() { + @Override + public Observable<Object> apply(Observable<Object> f) throws Exception { + return f.throttleLatest(1, TimeUnit.MINUTES); + } + }); + } + + @Test + public void disposed() { + TestHelper.checkDisposed( + Observable.never() + .throttleLatest(1, TimeUnit.MINUTES) + ); + } + + @Test + public void normal() { + TestScheduler sch = new TestScheduler(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.throttleLatest(1, TimeUnit.SECONDS, sch).test(); + + ps.onNext(1); + + to.assertValuesOnly(1); + + ps.onNext(2); + + to.assertValuesOnly(1); + + ps.onNext(3); + + to.assertValuesOnly(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3); + + ps.onNext(4); + + to.assertValuesOnly(1, 3); + + ps.onNext(5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3, 5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3, 5); + + ps.onNext(6); + + to.assertValuesOnly(1, 3, 5, 6); + + ps.onNext(7); + ps.onComplete(); + + to.assertResult(1, 3, 5, 6); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertResult(1, 3, 5, 6); + } + + @Test + public void normalEmitLast() { + TestScheduler sch = new TestScheduler(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.throttleLatest(1, TimeUnit.SECONDS, sch, true).test(); + + ps.onNext(1); + + to.assertValuesOnly(1); + + ps.onNext(2); + + to.assertValuesOnly(1); + + ps.onNext(3); + + to.assertValuesOnly(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3); + + ps.onNext(4); + + to.assertValuesOnly(1, 3); + + ps.onNext(5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3, 5); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertValuesOnly(1, 3, 5); + + ps.onNext(6); + + to.assertValuesOnly(1, 3, 5, 6); + + ps.onNext(7); + ps.onComplete(); + + to.assertResult(1, 3, 5, 6, 7); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertResult(1, 3, 5, 6, 7); + } + + @Test + public void take() throws Exception { + Action onCancel = mock(Action.class); + + Observable.range(1, 5) + .doOnDispose(onCancel) + .throttleLatest(1, TimeUnit.MINUTES) + .take(1) + .test() + .assertResult(1); + + verify(onCancel).run(); + } + + @Test + public void reentrantComplete() { + TestScheduler sch = new TestScheduler(); + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = new TestObserver<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + if (t == 1) { + ps.onNext(2); + } + if (t == 2) { + ps.onComplete(); + } + } + }; + + ps.throttleLatest(1, TimeUnit.SECONDS, sch).subscribe(to); + + ps.onNext(1); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + to.assertResult(1, 2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java index 3c1bfd1e2a..4542892ba6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeIntervalTest.java @@ -135,4 +135,15 @@ public void error() { .test() .assertFailure(TestException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Timed<Object>>>() { + @Override + public Observable<Timed<Object>> apply(Observable<Object> f) + throws Exception { + return f.timeInterval(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java index b32903164d..9d517ec1be 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java @@ -13,8 +13,8 @@ package io.reactivex.internal.operators.observable; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; @@ -50,24 +50,24 @@ public void setUp() { @Test public void shouldNotTimeoutIfOnNextWithinTimeout() { Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); - withTimeout.subscribe(ts); + withTimeout.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); verify(observer).onNext("One"); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); verify(observer, never()).onError(any(Throwable.class)); - ts.dispose(); + to.dispose(); } @Test public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); - withTimeout.subscribe(ts); + withTimeout.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); @@ -76,57 +76,53 @@ public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { verify(observer).onNext("Two"); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); verify(observer, never()).onError(any(Throwable.class)); - ts.dispose(); + to.dispose(); } @Test public void shouldTimeoutIfOnNextNotWithinTimeout() { - Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> observer = new TestObserver<String>(); - withTimeout.subscribe(ts); + withTimeout.subscribe(observer); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); - ts.dispose(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT)); } @Test public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { - Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> observer = new TestObserver<String>(); withTimeout.subscribe(observer); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); - verify(observer).onNext("One"); + observer.assertValue("One"); testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); - verify(observer).onError(any(TimeoutException.class)); - ts.dispose(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(TIMEOUT, TIME_UNIT), "One"); } @Test public void shouldCompleteIfUnderlyingComletes() { Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); withTimeout.subscribe(observer); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onComplete(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); verify(observer).onComplete(); verify(observer, never()).onError(any(Throwable.class)); - ts.dispose(); + to.dispose(); } @Test public void shouldErrorIfUnderlyingErrors() { Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); withTimeout.subscribe(observer); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onError(new UnsupportedOperationException()); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); verify(observer).onError(any(UnsupportedOperationException.class)); - ts.dispose(); + to.dispose(); } @Test @@ -135,8 +131,8 @@ public void shouldSwitchToOtherIfOnNextNotWithinTimeout() { Observable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - source.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + source.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); @@ -149,7 +145,7 @@ public void shouldSwitchToOtherIfOnNextNotWithinTimeout() { inOrder.verify(observer, times(1)).onNext("c"); inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - ts.dispose(); + to.dispose(); } @Test @@ -158,8 +154,8 @@ public void shouldSwitchToOtherIfOnErrorNotWithinTimeout() { Observable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - source.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + source.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); @@ -172,7 +168,7 @@ public void shouldSwitchToOtherIfOnErrorNotWithinTimeout() { inOrder.verify(observer, times(1)).onNext("c"); inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - ts.dispose(); + to.dispose(); } @Test @@ -181,8 +177,8 @@ public void shouldSwitchToOtherIfOnCompletedNotWithinTimeout() { Observable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - source.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + source.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); @@ -195,7 +191,7 @@ public void shouldSwitchToOtherIfOnCompletedNotWithinTimeout() { inOrder.verify(observer, times(1)).onNext("c"); inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); - ts.dispose(); + to.dispose(); } @Test @@ -204,8 +200,8 @@ public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { Observable<String> source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler, other); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - source.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + source.subscribe(to); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); underlyingSubject.onNext("One"); @@ -214,7 +210,7 @@ public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { other.onNext("a"); other.onNext("b"); - ts.dispose(); + to.dispose(); // The following messages should not be delivered. other.onNext("c"); @@ -234,8 +230,7 @@ public void shouldTimeoutIfSynchronizedObservableEmitFirstOnNextNotWithinTimeout final CountDownLatch exit = new CountDownLatch(1); final CountDownLatch timeoutSetuped = new CountDownLatch(1); - final Observer<String> observer = TestHelper.mockObserver(); - final TestObserver<String> ts = new TestObserver<String>(observer); + final TestObserver<String> observer = new TestObserver<String>(); new Thread(new Runnable() { @@ -257,16 +252,14 @@ public void subscribe(Observer<? super String> observer) { } }).timeout(1, TimeUnit.SECONDS, testScheduler) - .subscribe(ts); + .subscribe(observer); } }).start(); timeoutSetuped.await(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.SECONDS)); exit.countDown(); // exit the thread } @@ -274,41 +267,38 @@ public void subscribe(Observer<? super String> observer) { @Test public void shouldUnsubscribeFromUnderlyingSubscriptionOnTimeout() throws InterruptedException { // From https://github.com/ReactiveX/RxJava/pull/951 - final Disposable s = mock(Disposable.class); + final Disposable upstream = mock(Disposable.class); Observable<String> never = Observable.unsafeCreate(new ObservableSource<String>() { @Override public void subscribe(Observer<? super String> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); } }); TestScheduler testScheduler = new TestScheduler(); Observable<String> observableWithTimeout = never.timeout(1000, TimeUnit.MILLISECONDS, testScheduler); - Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - observableWithTimeout.subscribe(ts); + TestObserver<String> observer = new TestObserver<String>(); + observableWithTimeout.subscribe(observer); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer).onError(isA(TimeoutException.class)); - inOrder.verifyNoMoreInteractions(); + observer.assertFailureAndMessage(TimeoutException.class, timeoutMessage(1000, TimeUnit.MILLISECONDS)); - verify(s, times(1)).dispose(); + verify(upstream, times(1)).dispose(); } @Test @Ignore("s should be considered cancelled upon executing onComplete and not expect downstream to call cancel") public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyComplete() { // From https://github.com/ReactiveX/RxJava/pull/951 - final Disposable s = mock(Disposable.class); + final Disposable upstream = mock(Disposable.class); Observable<String> immediatelyComplete = Observable.unsafeCreate(new ObservableSource<String>() { @Override public void subscribe(Observer<? super String> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); observer.onComplete(); } }); @@ -318,8 +308,8 @@ public void subscribe(Observer<? super String> observer) { testScheduler); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - observableWithTimeout.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + observableWithTimeout.subscribe(to); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); @@ -327,19 +317,19 @@ public void subscribe(Observer<? super String> observer) { inOrder.verify(observer).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(s, times(1)).dispose(); + verify(upstream, times(1)).dispose(); } @Test @Ignore("s should be considered cancelled upon executing onError and not expect downstream to call cancel") public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyErrored() throws InterruptedException { // From https://github.com/ReactiveX/RxJava/pull/951 - final Disposable s = mock(Disposable.class); + final Disposable upstream = mock(Disposable.class); Observable<String> immediatelyError = Observable.unsafeCreate(new ObservableSource<String>() { @Override public void subscribe(Observer<? super String> observer) { - observer.onSubscribe(s); + observer.onSubscribe(upstream); observer.onError(new IOException("Error")); } }); @@ -349,8 +339,8 @@ public void subscribe(Observer<? super String> observer) { testScheduler); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - observableWithTimeout.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + observableWithTimeout.subscribe(to); testScheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); @@ -358,7 +348,7 @@ public void subscribe(Observer<? super String> observer) { inOrder.verify(observer).onError(isA(IOException.class)); inOrder.verifyNoMoreInteractions(); - verify(s, times(1)).dispose(); + verify(upstream, times(1)).dispose(); } @Test @@ -424,12 +414,6 @@ public void timedEmpty() { .assertResult(); } - @Test - public void newTimer() { - ObservableTimeoutTimed.NEW_TIMER.dispose(); - assertTrue(ObservableTimeoutTimed.NEW_TIMER.isDisposed()); - } - @Test public void badSource() { List<Throwable> errors = TestHelper.trackPluginErrors(); @@ -515,4 +499,91 @@ public void timedFallbackTake() { to.assertResult(1); } + + @Test + public void fallbackErrors() { + Observable.never() + .timeout(1, TimeUnit.MILLISECONDS, Observable.error(new TestException())) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + } + + @Test + public void onNextOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestScheduler sch = new TestScheduler(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.timeout(1, TimeUnit.SECONDS, sch).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; + + TestHelper.race(r1, r2); + + if (to.valueCount() != 0) { + if (to.errorCount() != 0) { + to.assertFailure(TimeoutException.class, 1); + to.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); + } else { + to.assertValuesOnly(1); + } + } else { + to.assertFailure(TimeoutException.class); + to.assertErrorMessage(timeoutMessage(1, TimeUnit.SECONDS)); + } + } + } + + @Test + public void onNextOnTimeoutRaceFallback() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestScheduler sch = new TestScheduler(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Integer> to = ps.timeout(1, TimeUnit.SECONDS, sch, Observable.just(2)).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; + + TestHelper.race(r1, r2); + + if (to.isTerminated()) { + int c = to.valueCount(); + if (c == 1) { + int v = to.values().get(0); + assertTrue("" + v, v == 1 || v == 2); + } else { + to.assertResult(1, 2); + } + } else { + to.assertValuesOnly(1); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java index a76affd787..a0d8a93ebe 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java @@ -14,12 +14,11 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import org.junit.Test; import org.mockito.InOrder; @@ -31,7 +30,7 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; @@ -328,14 +327,14 @@ public Void answer(InvocationOnMock invocation) throws Throwable { }).when(o).onComplete(); - final TestObserver<Integer> ts = new TestObserver<Integer>(o); + final TestObserver<Integer> to = new TestObserver<Integer>(o); new Thread(new Runnable() { @Override public void run() { PublishSubject<Integer> source = PublishSubject.create(); - source.timeout(timeoutFunc, Observable.just(3)).subscribe(ts); + source.timeout(timeoutFunc, Observable.just(3)).subscribe(to); source.onNext(1); // start timeout try { if (!enteredTimeoutOne.await(30, TimeUnit.SECONDS)) { @@ -546,4 +545,314 @@ public void selectorFallbackTake() { to.assertResult(1); } + + @Test + public void lateOnTimeoutError() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Observer<?>[] sub = { null, null }; + + final Observable<Integer> pp2 = new Observable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + sub[count++] = observer; + } + }; + + TestObserver<Integer> to = ps.timeout(Functions.justFunction(pp2)).test(); + + ps.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + final Throwable ex = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void lateOnTimeoutFallbackRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Observer<?>[] sub = { null, null }; + + final Observable<Integer> pp2 = new Observable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + assertFalse(((Disposable)observer).isDisposed()); + observer.onSubscribe(Disposables.empty()); + sub[count++] = observer; + } + }; + + TestObserver<Integer> to = ps.timeout(Functions.justFunction(pp2), Observable.<Integer>never()).test(); + + ps.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + final Throwable ex = new TestException(); + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onErrorOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Observer<?>[] sub = { null, null }; + + final Observable<Integer> pp2 = new Observable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + assertFalse(((Disposable)observer).isDisposed()); + observer.onSubscribe(Disposables.empty()); + sub[count++] = observer; + } + }; + + TestObserver<Integer> to = ps.timeout(Functions.justFunction(pp2)).test(); + + ps.onNext(0); + + final Throwable ex = new TestException(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onCompleteOnTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Observer<?>[] sub = { null, null }; + + final Observable<Integer> pp2 = new Observable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + assertFalse(((Disposable)observer).isDisposed()); + observer.onSubscribe(Disposables.empty()); + sub[count++] = observer; + } + }; + + TestObserver<Integer> to = ps.timeout(Functions.justFunction(pp2)).test(); + + ps.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void onCompleteOnTimeoutRaceFallback() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final PublishSubject<Integer> ps = PublishSubject.create(); + + final Observer<?>[] sub = { null, null }; + + final Observable<Integer> pp2 = new Observable<Integer>() { + + int count; + + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + assertFalse(((Disposable)observer).isDisposed()); + observer.onSubscribe(Disposables.empty()); + sub[count++] = observer; + } + }; + + TestObserver<Integer> to = ps.timeout(Functions.justFunction(pp2), Observable.<Integer>never()).test(); + + ps.onNext(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sub[0].onComplete(); + } + }; + + TestHelper.race(r1, r2); + + to.assertValueAt(0, 0); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void disposedUpfront() { + PublishSubject<Integer> ps = PublishSubject.create(); + final AtomicInteger counter = new AtomicInteger(); + + Observable<Object> timeoutAndFallback = Observable.never().doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + counter.incrementAndGet(); + } + }); + + ps + .timeout(timeoutAndFallback, Functions.justFunction(timeoutAndFallback)) + .test(true) + .assertEmpty(); + + assertEquals(0, counter.get()); + } + + @Test + public void disposedUpfrontFallback() { + PublishSubject<Object> ps = PublishSubject.create(); + final AtomicInteger counter = new AtomicInteger(); + + Observable<Object> timeoutAndFallback = Observable.never().doOnSubscribe(new Consumer<Disposable>() { + @Override + public void accept(Disposable d) throws Exception { + counter.incrementAndGet(); + } + }); + + ps + .timeout(timeoutAndFallback, Functions.justFunction(timeoutAndFallback), timeoutAndFallback) + .test(true) + .assertEmpty(); + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java index e94d5c1ec4..124283dc1f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java @@ -24,8 +24,10 @@ import org.mockito.*; import io.reactivex.*; +import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; +import io.reactivex.internal.operators.observable.ObservableTimer.TimerObserver; import io.reactivex.observables.ConnectableObservable; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; @@ -60,170 +62,172 @@ public void testTimerOnce() { @Test public void testTimerPeriodically() { - TestObserver<Long> ts = new TestObserver<Long>(); + TestObserver<Long> to = new TestObserver<Long>(); - Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler).subscribe(ts); + Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler).subscribe(to); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValue(0L); + to.assertValue(0L); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValues(0L, 1L); + to.assertValues(0L, 1L); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValues(0L, 1L, 2L); + to.assertValues(0L, 1L, 2L); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValues(0L, 1L, 2L, 3L); + to.assertValues(0L, 1L, 2L, 3L); - ts.dispose(); + to.dispose(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValues(0L, 1L, 2L, 3L); + to.assertValues(0L, 1L, 2L, 3L); - ts.assertNotComplete(); - ts.assertNoErrors(); + to.assertNotComplete(); + to.assertNoErrors(); } + @Test public void testInterval() { Observable<Long> w = Observable.interval(1, TimeUnit.SECONDS, scheduler); - TestObserver<Long> ts = new TestObserver<Long>(); - w.subscribe(ts); + TestObserver<Long> to = new TestObserver<Long>(); + w.subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertNotComplete(); scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - ts.assertValues(0L, 1L); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertValues(0L, 1L); + to.assertNoErrors(); + to.assertNotComplete(); - ts.dispose(); + to.dispose(); scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - ts.assertValues(0L, 1L); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertValues(0L, 1L); + to.assertNoErrors(); + to.assertNotComplete(); } @Test public void testWithMultipleSubscribersStartingAtSameTime() { Observable<Long> w = Observable.interval(1, TimeUnit.SECONDS, scheduler); - TestObserver<Long> ts1 = new TestObserver<Long>(); - TestObserver<Long> ts2 = new TestObserver<Long>(); + TestObserver<Long> to1 = new TestObserver<Long>(); + TestObserver<Long> to2 = new TestObserver<Long>(); - w.subscribe(ts1); - w.subscribe(ts2); + w.subscribe(to1); + w.subscribe(to2); - ts1.assertNoValues(); - ts2.assertNoValues(); + to1.assertNoValues(); + to2.assertNoValues(); scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - ts1.assertValues(0L, 1L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertValues(0L, 1L); - ts2.assertNoErrors(); - ts2.assertNotComplete(); + to2.assertValues(0L, 1L); + to2.assertNoErrors(); + to2.assertNotComplete(); - ts1.dispose(); - ts2.dispose(); + to1.dispose(); + to2.dispose(); scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - ts1.assertValues(0L, 1L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertValues(0L, 1L); - ts2.assertNoErrors(); - ts2.assertNotComplete(); + to2.assertValues(0L, 1L); + to2.assertNoErrors(); + to2.assertNotComplete(); } @Test public void testWithMultipleStaggeredSubscribers() { Observable<Long> w = Observable.interval(1, TimeUnit.SECONDS, scheduler); - TestObserver<Long> ts1 = new TestObserver<Long>(); + TestObserver<Long> to1 = new TestObserver<Long>(); - w.subscribe(ts1); + w.subscribe(to1); - ts1.assertNoErrors(); + to1.assertNoErrors(); scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - TestObserver<Long> ts2 = new TestObserver<Long>(); + TestObserver<Long> to2 = new TestObserver<Long>(); - w.subscribe(ts2); + w.subscribe(to2); - ts1.assertValues(0L, 1L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertNoValues(); + to2.assertNoValues(); scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - ts1.assertValues(0L, 1L, 2L, 3L); + to1.assertValues(0L, 1L, 2L, 3L); - ts2.assertValues(0L, 1L); + to2.assertValues(0L, 1L); - ts1.dispose(); - ts2.dispose(); + to1.dispose(); + to2.dispose(); - ts1.assertValues(0L, 1L, 2L, 3L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L, 2L, 3L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertValues(0L, 1L); - ts2.assertNoErrors(); - ts2.assertNotComplete(); + to2.assertValues(0L, 1L); + to2.assertNoErrors(); + to2.assertNotComplete(); } @Test public void testWithMultipleStaggeredSubscribersAndPublish() { ConnectableObservable<Long> w = Observable.interval(1, TimeUnit.SECONDS, scheduler).publish(); - TestObserver<Long> ts1 = new TestObserver<Long>(); + TestObserver<Long> to1 = new TestObserver<Long>(); - w.subscribe(ts1); + w.subscribe(to1); w.connect(); - ts1.assertNoValues(); + to1.assertNoValues(); scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - TestObserver<Long> ts2 = new TestObserver<Long>(); - w.subscribe(ts2); + TestObserver<Long> to2 = new TestObserver<Long>(); + w.subscribe(to2); - ts1.assertValues(0L, 1L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertNoValues(); + to2.assertNoValues(); scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - ts1.assertValues(0L, 1L, 2L, 3L); + to1.assertValues(0L, 1L, 2L, 3L); - ts2.assertValues(2L, 3L); + to2.assertValues(2L, 3L); - ts1.dispose(); - ts2.dispose(); + to1.dispose(); + to2.dispose(); - ts1.assertValues(0L, 1L, 2L, 3L); - ts1.assertNoErrors(); - ts1.assertNotComplete(); + to1.assertValues(0L, 1L, 2L, 3L); + to1.assertNoErrors(); + to1.assertNotComplete(); - ts2.assertValues(2L, 3L); - ts2.assertNoErrors(); - ts2.assertNotComplete(); + to2.assertValues(2L, 3L); + to2.assertNoErrors(); + to2.assertNotComplete(); } + @Test public void testOnceObserverThrows() { Observable<Long> source = Observable.timer(100, TimeUnit.MILLISECONDS, scheduler); @@ -252,6 +256,7 @@ public void onComplete() { verify(observer, never()).onNext(anyLong()); verify(observer, never()).onComplete(); } + @Test public void testPeriodicObserverThrows() { Observable<Long> source = Observable.interval(100, 100, TimeUnit.MILLISECONDS, scheduler); @@ -312,7 +317,7 @@ public void timerInterruptible() throws Exception { try { for (Scheduler s : new Scheduler[] { Schedulers.single(), Schedulers.computation(), Schedulers.newThread(), Schedulers.io(), Schedulers.from(exec) }) { final AtomicBoolean interrupted = new AtomicBoolean(); - TestObserver<Long> ts = Observable.timer(1, TimeUnit.MILLISECONDS, s) + TestObserver<Long> to = Observable.timer(1, TimeUnit.MILLISECONDS, s) .map(new Function<Long, Long>() { @Override public Long apply(Long v) throws Exception { @@ -328,7 +333,7 @@ public Long apply(Long v) throws Exception { Thread.sleep(500); - ts.cancel(); + to.cancel(); Thread.sleep(500); @@ -339,4 +344,16 @@ public Long apply(Long v) throws Exception { } } + @Test + public void cancelledAndRun() { + TestObserver<Long> to = new TestObserver<Long>(); + to.onSubscribe(Disposables.empty()); + TimerObserver tm = new TimerObserver(to); + + tm.dispose(); + + tm.run(); + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToFutureTest.java index 5de707f461..4cd7e26076 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToFutureTest.java @@ -35,11 +35,11 @@ public void testSuccess() throws Exception { Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); - Observable.fromFuture(future).subscribe(ts); + Observable.fromFuture(future).subscribe(to); - ts.dispose(); + to.dispose(); verify(o, times(1)).onNext(value); verify(o, times(1)).onComplete(); @@ -57,9 +57,9 @@ public void testSuccessOperatesOnSuppliedScheduler() throws Exception { Observer<Object> o = TestHelper.mockObserver(); TestScheduler scheduler = new TestScheduler(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); - Observable.fromFuture(future, scheduler).subscribe(ts); + Observable.fromFuture(future, scheduler).subscribe(to); verify(o, never()).onNext(value); @@ -77,11 +77,11 @@ public void testFailure() throws Exception { Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); - Observable.fromFuture(future).subscribe(ts); + Observable.fromFuture(future).subscribe(to); - ts.dispose(); + to.dispose(); verify(o, never()).onNext(null); verify(o, never()).onComplete(); @@ -98,13 +98,13 @@ public void testCancelledBeforeSubscribe() throws Exception { Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); - ts.dispose(); + TestObserver<Object> to = new TestObserver<Object>(o); + to.dispose(); - Observable.fromFuture(future).subscribe(ts); + Observable.fromFuture(future).subscribe(to); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertNoErrors(); + to.assertNotComplete(); } @Test @@ -144,17 +144,17 @@ public Object get(long timeout, TimeUnit unit) throws InterruptedException, Exec Observer<Object> o = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(o); + TestObserver<Object> to = new TestObserver<Object>(o); Observable<Object> futureObservable = Observable.fromFuture(future); - futureObservable.subscribeOn(Schedulers.computation()).subscribe(ts); + futureObservable.subscribeOn(Schedulers.computation()).subscribe(to); Thread.sleep(100); - ts.dispose(); + to.dispose(); - ts.assertNoErrors(); - ts.assertNoValues(); - ts.assertNotComplete(); + to.assertNoErrors(); + to.assertNoValues(); + to.assertNotComplete(); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java index 251d98f6b3..1f28cfc5d2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -26,6 +25,7 @@ import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; public class ObservableToListTest { @@ -108,10 +108,10 @@ public void capacityHintObservable() { @Test public void testList() { Observable<String> w = Observable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", "two", "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -119,10 +119,10 @@ public void testList() { @Test public void testListViaObservable() { Observable<String> w = Observable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", "two", "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -130,13 +130,13 @@ public void testListViaObservable() { @Test public void testListMultipleSubscribers() { Observable<String> w = Observable.fromIterable(Arrays.asList("one", "two", "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> o1 = TestHelper.mockSingleObserver(); - observable.subscribe(o1); + single.subscribe(o1); SingleObserver<List<String>> o2 = TestHelper.mockSingleObserver(); - observable.subscribe(o2); + single.subscribe(o2); List<String> expected = Arrays.asList("one", "two", "three"); @@ -151,10 +151,10 @@ public void testListMultipleSubscribers() { @Ignore("Null values are not allowed") public void testListWithNullValue() { Observable<String> w = Observable.fromIterable(Arrays.asList("one", null, "three")); - Single<List<String>> observable = w.toList(); + Single<List<String>> single = w.toList(); SingleObserver<List<String>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList("one", null, "three")); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -270,4 +270,22 @@ public Collection<Integer> call() throws Exception { .assertFailure(NullPointerException.class) .assertErrorMessage("The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<List<Object>>>() { + @Override + public Observable<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.toList().toObservable(); + } + }); + TestHelper.checkDoubleOnSubscribeObservableToSingle(new Function<Observable<Object>, Single<List<Object>>>() { + @Override + public Single<List<Object>> apply(Observable<Object> f) + throws Exception { + return f.toList(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java index 1f477a0a38..f9eb6e1634 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMapTest.java @@ -225,7 +225,6 @@ public String apply(String v) { verify(objectObserver, times(1)).onError(any(Throwable.class)); } - @Test public void testToMap() { Observable<String> source = Observable.just("a", "bb", "ccc", "dddd"); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java index 9a1ebc44d8..f762930fc1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToMultimapTest.java @@ -296,8 +296,6 @@ public Map<Integer, Collection<String>> call() { verify(objectObserver, never()).onComplete(); } - - @Test public void testToMultimap() { Observable<String> source = Observable.just("a", "b", "cc", "dd"); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java index b00cfbb483..20ffdd236a 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToSortedListTest.java @@ -115,14 +115,13 @@ public int compare(Integer a, Integer b) { .assertResult(Arrays.asList(5, 4, 3, 2, 1)); } - @Test public void testSortedList() { Observable<Integer> w = Observable.just(1, 3, 2, 5, 4); - Single<List<Integer>> observable = w.toSortedList(); + Single<List<Integer>> single = w.toSortedList(); SingleObserver<List<Integer>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList(1, 2, 3, 4, 5)); verify(observer, Mockito.never()).onError(any(Throwable.class)); } @@ -130,7 +129,7 @@ public void testSortedList() { @Test public void testSortedListWithCustomFunction() { Observable<Integer> w = Observable.just(1, 3, 2, 5, 4); - Single<List<Integer>> observable = w.toSortedList(new Comparator<Integer>() { + Single<List<Integer>> single = w.toSortedList(new Comparator<Integer>() { @Override public int compare(Integer t1, Integer t2) { @@ -140,7 +139,7 @@ public int compare(Integer t1, Integer t2) { }); SingleObserver<List<Integer>> observer = TestHelper.mockSingleObserver(); - observable.subscribe(observer); + single.subscribe(observer); verify(observer, times(1)).onSuccess(Arrays.asList(5, 4, 3, 2, 1)); verify(observer, Mockito.never()).onError(any(Throwable.class)); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java index 744c585646..1067ff37c9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java @@ -34,7 +34,7 @@ public class ObservableUnsubscribeOnTest { @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>(); @@ -46,13 +46,16 @@ public void subscribe(Observer<? super Integer> t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); TestObserver<Integer> observer = new TestObserver<Integer>(); - w.subscribeOn(UI_EVENT_LOOP).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + w.subscribeOn(uiEventLoop).observeOn(Schedulers.computation()) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(observer); @@ -69,18 +72,18 @@ public void subscribe(Observer<? super Integer> t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread.toString(), unsubscribeThread == UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread.toString(), unsubscribeThread, uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } @Test(timeout = 5000) public void unsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads() throws InterruptedException { - UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler(); + UIEventLoopScheduler uiEventLoop = new UIEventLoopScheduler(); try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>(); @@ -92,13 +95,16 @@ public void subscribe(Observer<? super Integer> t1) { t1.onSubscribe(subscription); t1.onNext(1); t1.onNext(2); - t1.onComplete(); + // observeOn will prevent canceling the upstream upon its termination now + // this call is racing for that state in this test + // not doing it will make sure the unsubscribeOn always gets through + // t1.onComplete(); } }); TestObserver<Integer> observer = new TestObserver<Integer>(); w.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.computation()) - .unsubscribeOn(UI_EVENT_LOOP) + .unsubscribeOn(uiEventLoop) .take(2) .subscribe(observer); @@ -113,15 +119,15 @@ public void subscribe(Observer<? super Integer> t1) { assertNotSame(Thread.currentThread(), subscribeThread.get()); // True for Schedulers.newThread() - System.out.println("UI Thread: " + UI_EVENT_LOOP.getThread()); + System.out.println("UI Thread: " + uiEventLoop.getThread()); System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertSame(unsubscribeThread, UI_EVENT_LOOP.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); } finally { - UI_EVENT_LOOP.shutdown(); + uiEventLoop.shutdown(); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java index 2bf61565d9..02fd41ef54 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUsingTest.java @@ -52,8 +52,8 @@ public void accept(Resource r) { private final Consumer<Disposable> disposeSubscription = new Consumer<Disposable>() { @Override - public void accept(Disposable s) { - s.dispose(); + public void accept(Disposable d) { + d.dispose(); } }; @@ -185,7 +185,7 @@ public Disposable call() { Function<Disposable, Observable<Integer>> observableFactory = new Function<Disposable, Observable<Integer>>() { @Override - public Observable<Integer> apply(Disposable s) { + public Observable<Integer> apply(Disposable d) { return Observable.empty(); } }; @@ -330,8 +330,6 @@ public Observable<String> apply(Resource resource) { } - - @Test public void testUsingDisposesEagerlyBeforeError() { final List<String> events = new ArrayList<String>(); @@ -567,4 +565,45 @@ public void sourceSupplierReturnsNull() { .assertFailureAndMessage(NullPointerException.class, "The sourceSupplier returned a null ObservableSource") ; } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() { + @Override + public ObservableSource<Object> apply(Observable<Object> o) + throws Exception { + return Observable.using(Functions.justCallable(1), Functions.justFunction(o), Functions.emptyConsumer()); + } + }); + } + + @Test + public void eagerDisposedOnComplete() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.using(Functions.justCallable(1), Functions.justFunction(new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + to.cancel(); + observer.onComplete(); + } + }), Functions.emptyConsumer(), true) + .subscribe(to); + } + + @Test + public void eagerDisposedOnError() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Observable.using(Functions.justCallable(1), Functions.justFunction(new Observable<Integer>() { + @Override + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + to.cancel(); + observer.onError(new TestException()); + } + }), Functions.emptyConsumer(), true) + .subscribe(to); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java index eeaf5be0de..9f8969222b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java @@ -14,22 +14,23 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.Test; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; +import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.subjects.*; public class ObservableWindowWithObservableTest { @@ -74,7 +75,6 @@ public void onComplete() { } source.onComplete(); - verify(o, never()).onError(any(Throwable.class)); assertEquals(n / 3, values.size()); @@ -251,7 +251,7 @@ public void onComplete() { @Test public void testWindowNoDuplication() { final PublishSubject<Integer> source = PublishSubject.create(); - final TestObserver<Integer> tsw = new TestObserver<Integer>() { + final TestObserver<Integer> tow = new TestObserver<Integer>() { boolean once; @Override public void onNext(Integer t) { @@ -262,10 +262,10 @@ public void onNext(Integer t) { super.onNext(t); } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>() { + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> t) { - t.subscribe(tsw); + t.subscribe(tow); super.onNext(t); } }; @@ -274,13 +274,13 @@ public void onNext(Observable<Integer> t) { public Observable<Object> call() { return Observable.never(); } - }).subscribe(ts); + }).subscribe(to); source.onNext(1); source.onComplete(); - ts.assertValueCount(1); - tsw.assertValues(1, 2); + to.assertValueCount(1); + tow.assertValues(1, 2); } @Test @@ -293,12 +293,12 @@ public Observable<String> call() { } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); - source.window(boundary).subscribe(ts); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); + source.window(boundary).subscribe(to); // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); - ts.assertComplete(); + to.assertComplete(); } @Test @@ -312,8 +312,8 @@ public Observable<Integer> call() { } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); - source.window(boundaryFunc).subscribe(ts); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); + source.window(boundaryFunc).subscribe(to); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); @@ -323,10 +323,11 @@ public Observable<Integer> call() { assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); - ts.assertComplete(); - ts.assertNoErrors(); - ts.assertValueCount(1); + to.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(1); } + @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishSubject<Integer> source = PublishSubject.create(); @@ -338,21 +339,20 @@ public Observable<Integer> call() { } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); - source.window(boundaryFunc).subscribe(ts); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); + source.window(boundaryFunc).subscribe(to); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); boundary.onComplete(); - // FIXME source still active because the open window - assertTrue(source.hasObservers()); + assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); - ts.assertComplete(); - ts.assertNoErrors(); - ts.assertValueCount(1); + to.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(1); } @Test @@ -366,22 +366,25 @@ public Observable<Integer> call() { } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); - source.window(boundaryFunc).subscribe(ts); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); + source.window(boundaryFunc).subscribe(to); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); - ts.dispose(); + to.dispose(); - // FIXME source has subscribers because the open window assertTrue(source.hasObservers()); - // FIXME boundary has subscribers because the open window - assertTrue(boundary.hasObservers()); - ts.assertNotComplete(); - ts.assertNoErrors(); - ts.assertValueCount(1); + assertFalse(boundary.hasObservers()); + + to.values().get(0).test(true); + + assertFalse(source.hasObservers()); + + to.assertNotComplete(); + to.assertNoErrors(); + to.assertValueCount(1); } @Test @@ -397,8 +400,8 @@ public Observable<Integer> call() { } }; - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); - source.window(boundaryFunc).subscribe(ts); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); + source.window(boundaryFunc).subscribe(to); source.onNext(1); boundary.onNext(1); @@ -415,9 +418,9 @@ public Observable<Integer> call() { source.onNext(4); source.onComplete(); - ts.assertNoErrors(); - ts.assertValueCount(4); - ts.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(4); + to.assertComplete(); assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); @@ -582,4 +585,683 @@ public ObservableSource<Object> apply(Observable<Object> v) throws Exception { } }, false, 1, 1, 1); } + + @Test + public void boundaryError() { + BehaviorSubject.createDefault(1) + .window(Functions.justCallable(Observable.error(new TestException()))) + .test() + .assertValueCount(1) + .assertNotComplete() + .assertError(TestException.class); + } + + @Test + public void boundaryCallableCrashOnCall2() { + BehaviorSubject.createDefault(1) + .window(new Callable<Observable<Integer>>() { + int calls; + @Override + public Observable<Integer> call() throws Exception { + if (++calls == 2) { + throw new TestException(); + } + return Observable.just(1); + } + }) + .test() + .assertError(TestException.class) + .assertNotComplete(); + } + + @Test + public void oneWindow() { + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<Observable<Integer>> to = BehaviorSubject.createDefault(1) + .window(Functions.justCallable(ps)) + .take(1) + .test(); + + ps.onNext(1); + + to + .assertValueCount(1) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void boundaryDirectDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Observable<Object>>>() { + @Override + public Observable<Observable<Object>> apply(Observable<Object> f) + throws Exception { + return f.window(Observable.never()).takeLast(1); + } + }); + } + + @Test + public void upstreamDisposedWhenOutputsDisposed() { + PublishSubject<Integer> source = PublishSubject.create(); + PublishSubject<Integer> boundary = PublishSubject.create(); + + TestObserver<Integer> to = source.window(boundary) + .take(1) + .flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply( + Observable<Integer> w) throws Exception { + return w.take(1); + } + }) + .test(); + + source.onNext(1); + + assertFalse("source not disposed", source.hasObservers()); + assertFalse("boundary not disposed", boundary.hasObservers()); + + to.assertResult(1); + } + + @Test + public void mainAndBoundaryBothError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = Observable.error(new TestException("main")) + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test(); + + to + .assertValueCount(1) + .assertError(TestException.class) + .assertErrorMessage("main") + .assertNotComplete(); + + ref.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void mainCompleteBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onComplete(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to + .assertValueCount(1) + .assertTerminated(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void mainNextBoundaryNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onNext(1); + } + }; + + TestHelper.race(r1, r2); + + to + .assertValueCount(2) + .assertNotComplete() + .assertNoErrors(); + } + } + + @Test + public void takeOneAnotherBoundary() { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + }) + .test(); + + to.assertValueCount(1) + .assertNotTerminated() + .cancel(); + + ref.get().onNext(1); + + to.assertValueCount(1) + .assertNotTerminated(); + } + + @Test + public void disposeMainBoundaryCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + final TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + final AtomicInteger counter = new AtomicInteger(); + observer.onSubscribe(new Disposable() { + + @Override + public void dispose() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public boolean isDisposed() { + return false; + } + }); + ref.set(observer); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Observer<Object> o = ref.get(); + o.onNext(1); + o.onComplete(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeMainBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + final TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + final AtomicInteger counter = new AtomicInteger(); + observer.onSubscribe(new Disposable() { + + @Override + public void dispose() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public boolean isDisposed() { + return false; + } + }); + ref.set(observer); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Observer<Object> o = ref.get(); + o.onNext(1); + o.onError(ex); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void boundarySupplierDoubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, Observable<Observable<Object>>>() { + @Override + public Observable<Observable<Object>> apply(Observable<Object> f) + throws Exception { + return f.window(Functions.justCallable(Observable.never())).takeLast(1); + } + }); + } + + @Test + public void selectorUpstreamDisposedWhenOutputsDisposed() { + PublishSubject<Integer> source = PublishSubject.create(); + PublishSubject<Integer> boundary = PublishSubject.create(); + + TestObserver<Integer> to = source.window(Functions.justCallable(boundary)) + .take(1) + .flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply( + Observable<Integer> w) throws Exception { + return w.take(1); + } + }) + .test(); + + source.onNext(1); + + assertFalse("source not disposed", source.hasObservers()); + assertFalse("boundary not disposed", boundary.hasObservers()); + + to.assertResult(1); + } + + @Test + public void supplierMainAndBoundaryBothError() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = Observable.error(new TestException("main")) + .window(Functions.justCallable(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + })) + .test(); + + to + .assertValueCount(1) + .assertError(TestException.class) + .assertErrorMessage("main") + .assertNotComplete(); + + ref.get().onError(new TestException("inner")); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void supplierMainCompleteBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(Functions.justCallable(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onComplete(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onError(ex); + } + }; + + TestHelper.race(r1, r2); + + to + .assertValueCount(1) + .assertTerminated(); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void supplierMainNextBoundaryNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(Functions.justCallable(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + refMain.get().onNext(1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ref.get().onNext(1); + } + }; + + TestHelper.race(r1, r2); + + to + .assertValueCount(2) + .assertNotComplete() + .assertNoErrors(); + } + } + + @Test + public void supplierTakeOneAnotherBoundary() { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(Functions.justCallable(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + ref.set(observer); + } + })) + .test(); + + to.assertValueCount(1) + .assertNotTerminated() + .cancel(); + + ref.get().onNext(1); + + to.assertValueCount(1) + .assertNotTerminated(); + } + + @Test + public void supplierDisposeMainBoundaryCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + final TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(Functions.justCallable(new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + final AtomicInteger counter = new AtomicInteger(); + observer.onSubscribe(new Disposable() { + + @Override + public void dispose() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public boolean isDisposed() { + return false; + } + }); + ref.set(observer); + } + })) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Observer<Object> o = ref.get(); + o.onNext(1); + o.onComplete(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void supplierDisposeMainBoundaryErrorRace() { + final TestException ex = new TestException(); + + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<? super Object>> refMain = new AtomicReference<Observer<? super Object>>(); + final AtomicReference<Observer<? super Object>> ref = new AtomicReference<Observer<? super Object>>(); + + final TestObserver<Observable<Object>> to = new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + refMain.set(observer); + } + } + .window(new Callable<ObservableSource<Object>>() { + int count; + @Override + public ObservableSource<Object> call() throws Exception { + if (++count > 1) { + return Observable.never(); + } + return (new Observable<Object>() { + @Override + protected void subscribeActual(Observer<? super Object> observer) { + final AtomicInteger counter = new AtomicInteger(); + observer.onSubscribe(new Disposable() { + + @Override + public void dispose() { + // about a microsecond + for (int i = 0; i < 100; i++) { + counter.incrementAndGet(); + } + } + + @Override + public boolean isDisposed() { + return false; + } + }); + ref.set(observer); + } + }); + } + }) + .test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + Observer<Object> o = ref.get(); + o.onNext(1); + o.onError(ex); + } + }; + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java index 5be74817a8..af1503e8a7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithSizeTest.java @@ -105,7 +105,7 @@ public void testSkipAndCountWindowsWithGaps() { @Test public void testWindowUnsubscribeNonOverlapping() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger count = new AtomicInteger(); Observable.merge(Observable.range(1, 10000).doOnNext(new Consumer<Integer>() { @@ -114,17 +114,17 @@ public void accept(Integer t1) { count.incrementAndGet(); } - }).window(5).take(2)).subscribe(ts); - ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + }).window(5).take(2)).subscribe(to); + to.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); + to.assertTerminated(); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // System.out.println(ts.getOnNextEvents()); assertEquals(10, count.get()); } @Test public void testWindowUnsubscribeNonOverlappingAsyncSource() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger count = new AtomicInteger(); Observable.merge(Observable.range(1, 100000) .doOnNext(new Consumer<Integer>() { @@ -145,17 +145,17 @@ public void accept(Integer t1) { .observeOn(Schedulers.computation()) .window(5) .take(2)) - .subscribe(ts); - ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + .subscribe(to); + to.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); + to.assertTerminated(); + to.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // make sure we don't emit all values ... the unsubscribe should propagate assertTrue(count.get() < 100000); } @Test public void testWindowUnsubscribeOverlapping() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger count = new AtomicInteger(); Observable.merge(Observable.range(1, 10000).doOnNext(new Consumer<Integer>() { @@ -164,17 +164,17 @@ public void accept(Integer t1) { count.incrementAndGet(); } - }).window(5, 4).take(2)).subscribe(ts); - ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); + }).window(5, 4).take(2)).subscribe(to); + to.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); + to.assertTerminated(); // System.out.println(ts.getOnNextEvents()); - ts.assertValues(1, 2, 3, 4, 5, 5, 6, 7, 8, 9); + to.assertValues(1, 2, 3, 4, 5, 5, 6, 7, 8, 9); assertEquals(9, count.get()); } @Test public void testWindowUnsubscribeOverlappingAsyncSource() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger count = new AtomicInteger(); Observable.merge(Observable.range(1, 100000) .doOnNext(new Consumer<Integer>() { @@ -188,10 +188,10 @@ public void accept(Integer t1) { .observeOn(Schedulers.computation()) .window(5, 4) .take(2), 128) - .subscribe(ts); - ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); - ts.assertTerminated(); - ts.assertValues(1, 2, 3, 4, 5, 5, 6, 7, 8, 9); + .subscribe(to); + to.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); + to.assertTerminated(); + to.assertValues(1, 2, 3, 4, 5, 5, 6, 7, 8, 9); // make sure we don't emit all values ... the unsubscribe should propagate // assertTrue(count.get() < 100000); // disabled: a small hiccup in the consumption may allow the source to run to completion } @@ -204,17 +204,16 @@ private List<String> list(String... args) { return list; } - public static Observable<Integer> hotStream() { return Observable.unsafeCreate(new ObservableSource<Integer>() { @Override - public void subscribe(Observer<? super Integer> s) { + public void subscribe(Observer<? super Integer> observer) { Disposable d = Disposables.empty(); - s.onSubscribe(d); + observer.onSubscribe(d); while (!d.isDisposed()) { // burst some number of items for (int i = 0; i < Math.random() * 20; i++) { - s.onNext(i); + observer.onNext(i); } try { // sleep for a random amount of time @@ -231,7 +230,7 @@ public void subscribe(Observer<? super Integer> s) { @Test public void testTakeFlatMapCompletes() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final int indicator = 999999999; @@ -243,11 +242,11 @@ public void testTakeFlatMapCompletes() { public Observable<Integer> apply(Observable<Integer> w) { return w.startWith(indicator); } - }).subscribe(ts); + }).subscribe(to); - ts.awaitTerminalEvent(2, TimeUnit.SECONDS); - ts.assertComplete(); - ts.assertValueCount(22); + to.awaitTerminalEvent(2, TimeUnit.SECONDS); + to.assertComplete(); + to.assertValueCount(22); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java index 86e234961a..c4f7fa6409 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithStartEndObservableTest.java @@ -17,6 +17,7 @@ import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; @@ -28,6 +29,7 @@ import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subjects.*; @@ -201,14 +203,14 @@ public void testNoUnsubscribeAndNoLeak() { PublishSubject<Integer> open = PublishSubject.create(); final PublishSubject<Integer> close = PublishSubject.create(); - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); source.window(open, new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { return close; } - }).subscribe(ts); + }).subscribe(to); open.onNext(1); source.onNext(1); @@ -222,9 +224,9 @@ public Observable<Integer> apply(Integer t) { source.onComplete(); - ts.assertComplete(); - ts.assertNoErrors(); - ts.assertValueCount(1); + to.assertComplete(); + to.assertNoErrors(); + to.assertValueCount(1); // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); @@ -239,24 +241,24 @@ public void testUnsubscribeAll() { PublishSubject<Integer> open = PublishSubject.create(); final PublishSubject<Integer> close = PublishSubject.create(); - TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); + TestObserver<Observable<Integer>> to = new TestObserver<Observable<Integer>>(); source.window(open, new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { return close; } - }).subscribe(ts); + }).subscribe(to); open.onNext(1); assertTrue(open.hasObservers()); assertTrue(close.hasObservers()); - ts.dispose(); + to.dispose(); - // FIXME subject has subscribers because of the open window - assertTrue(open.hasObservers()); + // Disposing the outer sequence stops the opening of new windows + assertFalse(open.hasObservers()); // FIXME subject has subscribers because of the open window assertTrue(close.hasObservers()); } @@ -391,4 +393,69 @@ public Object apply(Observable<Object> o) throws Exception { } }, false, 1, 1, (Object[])null); } + + @Test + public void windowCloseIngoresCancel() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorSubject.createDefault(1) + .window(BehaviorSubject.createDefault(1), new Function<Integer, Observable<Integer>>() { + @Override + public Observable<Integer> apply(Integer f) throws Exception { + return new Observable<Integer>() { + @Override + protected void subscribeActual( + Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new TestException()); + } + }; + } + }) + .test() + .assertValueCount(1) + .assertNoErrors() + .assertNotComplete(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static Observable<Integer> observableDisposed(final AtomicBoolean ref) { + return Observable.just(1).concatWith(Observable.<Integer>never()) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + ref.set(true); + } + }); + } + + @Test + public void mainAndBoundaryDisposeOnNoWindows() { + AtomicBoolean mainDisposed = new AtomicBoolean(); + AtomicBoolean openDisposed = new AtomicBoolean(); + final AtomicBoolean closeDisposed = new AtomicBoolean(); + + observableDisposed(mainDisposed) + .window(observableDisposed(openDisposed), new Function<Integer, ObservableSource<Integer>>() { + @Override + public ObservableSource<Integer> apply(Integer v) throws Exception { + return observableDisposed(closeDisposed); + } + }) + .test() + .assertSubscribed() + .assertNoErrors() + .assertNotComplete() + .dispose(); + + assertTrue(mainDisposed.get()); + assertTrue(openDisposed.get()); + assertTrue(closeDisposed.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java index d481991a24..8500d52948 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java @@ -16,8 +16,8 @@ import static org.junit.Assert.*; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; @@ -32,7 +32,6 @@ import io.reactivex.schedulers.*; import io.reactivex.subjects.*; - public class ObservableWindowWithTimeTest { private TestScheduler scheduler; @@ -65,17 +64,19 @@ public void subscribe(Observer<? super String> observer) { Observable<Observable<String>> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler, 2); windowed.subscribe(observeWindow(list, lists)); - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(95, TimeUnit.MILLISECONDS); assertEquals(1, lists.size()); assertEquals(lists.get(0), list("one", "two")); - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("three", "four")); + scheduler.advanceTimeTo(195, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertTrue(lists.get(1).isEmpty()); + assertEquals(lists.get(2), list("three", "four")); scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(2), list("five")); + assertEquals(5, lists.size()); + assertTrue(lists.get(3).isEmpty()); + assertEquals(lists.get(4), list("five")); } @Test @@ -158,6 +159,7 @@ public void onNext(T args) { } }; } + @Test public void testExactWindowSize() { Observable<Observable<Integer>> source = Observable.range(1, 10) @@ -181,7 +183,7 @@ public void testExactWindowSize() { @Test public void testTakeFlatMapCompletes() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); final AtomicInteger wip = new AtomicInteger(); @@ -215,11 +217,11 @@ public void accept(Integer pv) { System.out.println(pv); } }) - .subscribe(ts); + .subscribe(to); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); - ts.assertComplete(); - Assert.assertTrue(ts.valueCount() != 0); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); + to.assertComplete(); + Assert.assertTrue(to.valueCount() != 0); } @Test @@ -306,107 +308,107 @@ public void timeskipJustSkip() { public void timeskipSkipping() { TestScheduler scheduler = new TestScheduler(); - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler) + TestObserver<Integer> to = ps.window(1, 2, TimeUnit.SECONDS, scheduler) .flatMap(Functions.<Observable<Integer>>identity()) .test(); - pp.onNext(1); - pp.onNext(2); + ps.onNext(1); + ps.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(3); - pp.onNext(4); + ps.onNext(3); + ps.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(5); - pp.onNext(6); + ps.onNext(5); + ps.onNext(6); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(7); - pp.onComplete(); + ps.onNext(7); + ps.onComplete(); - ts.assertResult(1, 2, 5, 6); + to.assertResult(1, 2, 5, 6); } @Test public void timeskipOverlapping() { TestScheduler scheduler = new TestScheduler(); - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) + TestObserver<Integer> to = ps.window(2, 1, TimeUnit.SECONDS, scheduler) .flatMap(Functions.<Observable<Integer>>identity()) .test(); - pp.onNext(1); - pp.onNext(2); + ps.onNext(1); + ps.onNext(2); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(3); - pp.onNext(4); + ps.onNext(3); + ps.onNext(4); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(5); - pp.onNext(6); + ps.onNext(5); + ps.onNext(6); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - pp.onNext(7); - pp.onComplete(); + ps.onNext(7); + ps.onComplete(); - ts.assertResult(1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7); + to.assertResult(1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7); } @Test public void exactOnError() { TestScheduler scheduler = new TestScheduler(); - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler) + TestObserver<Integer> to = ps.window(1, 1, TimeUnit.SECONDS, scheduler) .flatMap(Functions.<Observable<Integer>>identity()) .test(); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void overlappingOnError() { TestScheduler scheduler = new TestScheduler(); - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler) + TestObserver<Integer> to = ps.window(2, 1, TimeUnit.SECONDS, scheduler) .flatMap(Functions.<Observable<Integer>>identity()) .test(); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void skipOnError() { TestScheduler scheduler = new TestScheduler(); - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler) + TestObserver<Integer> to = ps.window(1, 2, TimeUnit.SECONDS, scheduler) .flatMap(Functions.<Observable<Integer>>identity()) .test(); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -591,17 +593,17 @@ public void sizeTimeTimeout() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 100) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 100) .test() .assertValueCount(1); scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS); - ts.assertValueCount(2) + to.assertValueCount(2) .assertNoErrors() .assertNotComplete(); - ts.values().get(0).test().assertResult(); + to.values().get(0).test().assertResult(); } @Test @@ -609,12 +611,12 @@ public void periodicWindowCompletion() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, false) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, false) .test(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValueCount(21) + to.assertValueCount(21) .assertNoErrors() .assertNotComplete(); } @@ -624,12 +626,12 @@ public void periodicWindowCompletionRestartTimer() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, true) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, true) .test(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValueCount(21) + to.assertValueCount(21) .assertNoErrors() .assertNotComplete(); } @@ -639,12 +641,12 @@ public void periodicWindowCompletionBounded() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, false) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, false) .test(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValueCount(21) + to.assertValueCount(21) .assertNoErrors() .assertNotComplete(); } @@ -654,12 +656,12 @@ public void periodicWindowCompletionRestartTimerBounded() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true) .test(); scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValueCount(21) + to.assertValueCount(21) .assertNoErrors() .assertNotComplete(); } @@ -669,7 +671,7 @@ public void periodicWindowCompletionRestartTimerBoundedSomeData() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 2, true) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 2, true) .test(); ps.onNext(1); @@ -677,7 +679,7 @@ public void periodicWindowCompletionRestartTimerBoundedSomeData() { scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); - ts.assertValueCount(22) + to.assertValueCount(22) .assertNoErrors() .assertNotComplete(); } @@ -687,7 +689,7 @@ public void countRestartsOnTimeTick() { TestScheduler scheduler = new TestScheduler(); Subject<Integer> ps = PublishSubject.<Integer>create(); - TestObserver<Observable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true) + TestObserver<Observable<Integer>> to = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true) .test(); // window #1 @@ -702,8 +704,248 @@ public void countRestartsOnTimeTick() { ps.onNext(5); ps.onNext(6); - ts.assertValueCount(2) + to.assertValueCount(2) .assertNoErrors() .assertNotComplete(); } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject<Integer> ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer<Observable<Integer>>() { + int count; + @Override + public void accept(Observable<Integer> v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java index 4cba999e51..713772c535 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromTest.java @@ -89,9 +89,9 @@ public void testEmptySource() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -100,9 +100,9 @@ public void testEmptySource() { source.onComplete(); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertNoValues(); + to.assertNoErrors(); + to.assertTerminated(); + to.assertNoValues(); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); @@ -115,9 +115,9 @@ public void testEmptyOther() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -126,15 +126,14 @@ public void testEmptyOther() { source.onComplete(); - ts.assertNoErrors(); - ts.assertTerminated(); - ts.assertNoValues(); + to.assertNoErrors(); + to.assertTerminated(); + to.assertNoValues(); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } - @Test public void testUnsubscription() { PublishSubject<Integer> source = PublishSubject.create(); @@ -142,9 +141,9 @@ public void testUnsubscription() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -152,11 +151,11 @@ public void testUnsubscription() { other.onNext(1); source.onNext(1); - ts.dispose(); + to.dispose(); - ts.assertValue((1 << 8) + 1); - ts.assertNoErrors(); - ts.assertNotComplete(); + to.assertValue((1 << 8) + 1); + to.assertNoErrors(); + to.assertNotComplete(); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); @@ -169,9 +168,9 @@ public void testSourceThrows() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -181,14 +180,15 @@ public void testSourceThrows() { source.onError(new TestException()); - ts.assertTerminated(); - ts.assertValue((1 << 8) + 1); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertTerminated(); + to.assertValue((1 << 8) + 1); + to.assertError(TestException.class); + to.assertNotComplete(); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); } + @Test public void testOtherThrows() { PublishSubject<Integer> source = PublishSubject.create(); @@ -196,9 +196,9 @@ public void testOtherThrows() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -208,10 +208,10 @@ public void testOtherThrows() { other.onError(new TestException()); - ts.assertTerminated(); - ts.assertValue((1 << 8) + 1); - ts.assertNotComplete(); - ts.assertError(TestException.class); + to.assertTerminated(); + to.assertValue((1 << 8) + 1); + to.assertNotComplete(); + to.assertError(TestException.class); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); @@ -224,9 +224,9 @@ public void testFunctionThrows() { Observable<Integer> result = source.withLatestFrom(other, COMBINER_ERROR); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); assertTrue(source.hasObservers()); assertTrue(other.hasObservers()); @@ -234,10 +234,10 @@ public void testFunctionThrows() { other.onNext(1); source.onNext(1); - ts.assertTerminated(); - ts.assertNotComplete(); - ts.assertNoValues(); - ts.assertError(TestException.class); + to.assertTerminated(); + to.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); assertFalse(source.hasObservers()); assertFalse(other.hasObservers()); @@ -250,9 +250,9 @@ public void testNoDownstreamUnsubscribe() { Observable<Integer> result = source.withLatestFrom(other, COMBINER); - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - result.subscribe(ts); + result.subscribe(to); source.onComplete(); @@ -260,7 +260,6 @@ public void testNoDownstreamUnsubscribe() { // assertTrue("Not cancelled!", ts.isCancelled()); } - static final Function<Object[], String> toArray = new Function<Object[], String>() { @Override public String apply(Object[] args) { @@ -275,40 +274,40 @@ public void manySources() { PublishSubject<String> ps3 = PublishSubject.create(); PublishSubject<String> main = PublishSubject.create(); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); main.withLatestFrom(new Observable[] { ps1, ps2, ps3 }, toArray) - .subscribe(ts); + .subscribe(to); main.onNext("1"); - ts.assertNoValues(); + to.assertNoValues(); ps1.onNext("a"); - ts.assertNoValues(); + to.assertNoValues(); ps2.onNext("A"); - ts.assertNoValues(); + to.assertNoValues(); ps3.onNext("="); - ts.assertNoValues(); + to.assertNoValues(); main.onNext("2"); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps2.onNext("B"); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps3.onComplete(); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps1.onNext("b"); main.onNext("3"); - ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); + to.assertValues("[2, a, A, =]", "[3, b, B, =]"); main.onComplete(); - ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues("[2, a, A, =]", "[3, b, B, =]"); + to.assertNoErrors(); + to.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasObservers()); assertFalse("ps2 has subscribers?", ps2.hasObservers()); @@ -322,40 +321,40 @@ public void manySourcesIterable() { PublishSubject<String> ps3 = PublishSubject.create(); PublishSubject<String> main = PublishSubject.create(); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); main.withLatestFrom(Arrays.<Observable<?>>asList(ps1, ps2, ps3), toArray) - .subscribe(ts); + .subscribe(to); main.onNext("1"); - ts.assertNoValues(); + to.assertNoValues(); ps1.onNext("a"); - ts.assertNoValues(); + to.assertNoValues(); ps2.onNext("A"); - ts.assertNoValues(); + to.assertNoValues(); ps3.onNext("="); - ts.assertNoValues(); + to.assertNoValues(); main.onNext("2"); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps2.onNext("B"); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps3.onComplete(); - ts.assertValues("[2, a, A, =]"); + to.assertValues("[2, a, A, =]"); ps1.onNext("b"); main.onNext("3"); - ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); + to.assertValues("[2, a, A, =]", "[3, b, B, =]"); main.onComplete(); - ts.assertValues("[2, a, A, =]", "[3, b, B, =]"); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValues("[2, a, A, =]", "[3, b, B, =]"); + to.assertNoErrors(); + to.assertComplete(); assertFalse("ps1 has subscribers?", ps1.hasObservers()); assertFalse("ps2 has subscribers?", ps2.hasObservers()); @@ -376,20 +375,20 @@ public void manySourcesIterableSweep() { expected.add(String.valueOf(val)); } - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); PublishSubject<String> main = PublishSubject.create(); - main.withLatestFrom(sources, toArray).subscribe(ts); + main.withLatestFrom(sources, toArray).subscribe(to); - ts.assertNoValues(); + to.assertNoValues(); main.onNext(val); main.onComplete(); - ts.assertValue(expected.toString()); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(expected.toString()); + to.assertNoErrors(); + to.assertComplete(); } } } @@ -400,18 +399,18 @@ public void backpressureNoSignal() { // PublishSubject<String> ps1 = PublishSubject.create(); // PublishSubject<String> ps2 = PublishSubject.create(); // -// TestObserver<String> ts = new TestObserver<String>(); +// TestObserver<String> to = new TestObserver<String>(); // // Observable.range(1, 10).withLatestFrom(new Observable<?>[] { ps1, ps2 }, toArray) -// .subscribe(ts); +// .subscribe(to); // -// ts.assertNoValues(); +// to.assertNoValues(); // -// ts.request(1); +// to.request(1); // -// ts.assertNoValues(); -// ts.assertNoErrors(); -// ts.assertComplete(); +// to.assertNoValues(); +// to.assertNoErrors(); +// to.assertComplete(); // // assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); // assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); @@ -423,29 +422,29 @@ public void backpressureWithSignal() { // PublishSubject<String> ps1 = PublishSubject.create(); // PublishSubject<String> ps2 = PublishSubject.create(); // -// TestObserver<String> ts = new TestObserver<String>(); +// TestObserver<String> to = new TestObserver<String>(); // // Observable.range(1, 3).withLatestFrom(new Observable<?>[] { ps1, ps2 }, toArray) // .subscribe(ts); // -// ts.assertNoValues(); +// to.assertNoValues(); // // ps1.onNext("1"); // ps2.onNext("1"); // -// ts.request(1); +// to.request(1); // -// ts.assertValue("[1, 1, 1]"); +// to.assertValue("[1, 1, 1]"); // -// ts.request(1); +// to.request(1); // -// ts.assertValues("[1, 1, 1]", "[2, 1, 1]"); +// to.assertValues("[1, 1, 1]", "[2, 1, 1]"); // -// ts.request(1); +// to.request(1); // -// ts.assertValues("[1, 1, 1]", "[2, 1, 1]", "[3, 1, 1]"); -// ts.assertNoErrors(); -// ts.assertComplete(); +// to.assertValues("[1, 1, 1]", "[2, 1, 1]", "[3, 1, 1]"); +// to.assertNoErrors(); +// to.assertComplete(); // // assertFalse("ps1 has subscribers?", ps1.hasSubscribers()); // assertFalse("ps2 has subscribers?", ps2.hasSubscribers()); @@ -453,48 +452,48 @@ public void backpressureWithSignal() { @Test public void withEmpty() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.range(1, 3).withLatestFrom( new Observable<?>[] { Observable.just(1), Observable.empty() }, toArray) - .subscribe(ts); + .subscribe(to); - ts.assertNoValues(); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertNoValues(); + to.assertNoErrors(); + to.assertComplete(); } @Test public void withError() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.range(1, 3).withLatestFrom( new Observable<?>[] { Observable.just(1), Observable.error(new TestException()) }, toArray) - .subscribe(ts); + .subscribe(to); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test public void withMainError() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.error(new TestException()).withLatestFrom( new Observable<?>[] { Observable.just(1), Observable.just(1) }, toArray) - .subscribe(ts); + .subscribe(to); - ts.assertNoValues(); - ts.assertError(TestException.class); - ts.assertNotComplete(); + to.assertNoValues(); + to.assertError(TestException.class); + to.assertNotComplete(); } @Test public void with2Others() { Observable<Integer> just = Observable.just(1); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); just.withLatestFrom(just, just, new Function3<Integer, Integer, Integer, List<Integer>>() { @Override @@ -502,18 +501,18 @@ public List<Integer> apply(Integer a, Integer b, Integer c) { return Arrays.asList(a, b, c); } }) - .subscribe(ts); + .subscribe(to); - ts.assertValue(Arrays.asList(1, 1, 1)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 1, 1)); + to.assertNoErrors(); + to.assertComplete(); } @Test public void with3Others() { Observable<Integer> just = Observable.just(1); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); just.withLatestFrom(just, just, just, new Function4<Integer, Integer, Integer, Integer, List<Integer>>() { @Override @@ -521,18 +520,18 @@ public List<Integer> apply(Integer a, Integer b, Integer c, Integer d) { return Arrays.asList(a, b, c, d); } }) - .subscribe(ts); + .subscribe(to); - ts.assertValue(Arrays.asList(1, 1, 1, 1)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 1, 1, 1)); + to.assertNoErrors(); + to.assertComplete(); } @Test public void with4Others() { Observable<Integer> just = Observable.just(1); - TestObserver<List<Integer>> ts = new TestObserver<List<Integer>>(); + TestObserver<List<Integer>> to = new TestObserver<List<Integer>>(); just.withLatestFrom(just, just, just, just, new Function5<Integer, Integer, Integer, Integer, Integer, List<Integer>>() { @Override @@ -540,11 +539,11 @@ public List<Integer> apply(Integer a, Integer b, Integer c, Integer d, Integer e return Arrays.asList(a, b, c, d, e); } }) - .subscribe(ts); + .subscribe(to); - ts.assertValue(Arrays.asList(1, 1, 1, 1, 1)); - ts.assertNoErrors(); - ts.assertComplete(); + to.assertValue(Arrays.asList(1, 1, 1, 1, 1)); + to.assertNoErrors(); + to.assertComplete(); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java index 971359d8ad..f05c41cfd3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java index 475cceeca7..eca18c71b3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -349,9 +348,9 @@ public void testAggregatorUnsubscribe() { PublishSubject<String> r2 = PublishSubject.create(); /* define an Observer to receive aggregated events */ Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); + TestObserver<String> to = new TestObserver<String>(observer); - Observable.zip(r1, r2, zipr2).subscribe(ts); + Observable.zip(r1, r2, zipr2).subscribe(to); /* simulate the Observables pushing data into the aggregator */ r1.onNext("hello"); @@ -361,7 +360,7 @@ public void testAggregatorUnsubscribe() { verify(observer, never()).onComplete(); verify(observer, times(1)).onNext("helloworld"); - ts.dispose(); + to.dispose(); r1.onNext("hello"); r2.onNext("again"); @@ -794,16 +793,16 @@ public String apply(Integer a, Integer b) { } }).take(5); - TestObserver<String> ts = new TestObserver<String>(); - os.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(); + os.subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); + to.awaitTerminalEvent(); + to.assertNoErrors(); - assertEquals(5, ts.valueCount()); - assertEquals("1-1", ts.values().get(0)); - assertEquals("2-2", ts.values().get(1)); - assertEquals("5-5", ts.values().get(4)); + assertEquals(5, to.valueCount()); + assertEquals("1-1", to.values().get(0)); + assertEquals("2-2", to.values().get(1)); + assertEquals("5-5", to.values().get(4)); } @Test @@ -969,10 +968,10 @@ public Object apply(final Object[] args) { } }); - TestObserver<Object> ts = new TestObserver<Object>(); - o.subscribe(ts); - ts.awaitTerminalEvent(200, TimeUnit.MILLISECONDS); - ts.assertNoValues(); + TestObserver<Object> to = new TestObserver<Object>(); + o.subscribe(to); + to.awaitTerminalEvent(200, TimeUnit.MILLISECONDS); + to.assertNoValues(); } /** @@ -1003,7 +1002,7 @@ public void testDownstreamBackpressureRequestsWithFiniteSyncObservables() { Observable<Integer> o1 = createInfiniteObservable(generatedA).take(Observable.bufferSize() * 2); Observable<Integer> o2 = createInfiniteObservable(generatedB).take(Observable.bufferSize() * 2); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.zip(o1, o2, new BiFunction<Integer, Integer, String>() { @Override @@ -1011,11 +1010,11 @@ public String apply(Integer t1, Integer t2) { return t1 + "-" + t2; } - }).observeOn(Schedulers.computation()).take(Observable.bufferSize() * 2).subscribe(ts); + }).observeOn(Schedulers.computation()).take(Observable.bufferSize() * 2).subscribe(to); - ts.awaitTerminalEvent(); - ts.assertNoErrors(); - assertEquals(Observable.bufferSize() * 2, ts.valueCount()); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Observable.bufferSize() * 2, to.valueCount()); System.out.println("Generated => A: " + generatedA.get() + " B: " + generatedB.get()); assertTrue(generatedA.get() < (Observable.bufferSize() * 3)); assertTrue(generatedB.get() < (Observable.bufferSize() * 3)); @@ -1261,6 +1260,7 @@ public Object apply(Integer a, Integer b, Integer c, Integer d, Integer e, Integ .test() .assertResult("12345678"); } + @Test public void zip9() { Observable.zip(Observable.just(1), @@ -1350,6 +1350,34 @@ public Object apply(Object[] a) throws Exception { .assertResult("[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]"); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void zipIterableNotSubclassingObservable() { + final ObservableSource<Integer> s1 = new ObservableSource<Integer>() { + @Override + public void subscribe (final Observer<? super Integer> observer) { + Observable.just(1).subscribe(observer); + } + }; + final ObservableSource<Integer> s2 = new ObservableSource<Integer>() { + @Override + public void subscribe (final Observer<? super Integer> observer) { + Observable.just(2).subscribe(observer); + } + }; + + Observable.zip(Arrays.asList(s1, s2), new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return Arrays.toString(a); + } + }) + .test() + .assertResult("[1, 2]"); + } + @Test public void dispose() { TestHelper.checkDisposed(Observable.zip(Observable.just(1), Observable.just(1), new BiFunction<Integer, Integer, Object>() { @@ -1363,7 +1391,7 @@ public Object apply(Integer a, Integer b) throws Exception { @Test public void noCrossBoundaryFusion() { for (int i = 0; i < 500; i++) { - TestObserver<List<Object>> ts = Observable.zip( + TestObserver<List<Object>> to = Observable.zip( Observable.just(1).observeOn(Schedulers.single()).map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { @@ -1387,7 +1415,7 @@ public List<Object> apply(Object t1, Object t2) throws Exception { .awaitDone(5, TimeUnit.SECONDS) .assertValueCount(1); - List<Object> list = ts.values().get(0); + List<Object> list = to.values().get(0); assertTrue(list.toString(), list.contains("RxSi")); assertTrue(list.toString(), list.contains("RxCo")); @@ -1399,7 +1427,7 @@ public void eagerDispose() { final PublishSubject<Integer> ps1 = PublishSubject.create(); final PublishSubject<Integer> ps2 = PublishSubject.create(); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -1421,10 +1449,41 @@ public Integer apply(Integer t1, Integer t2) throws Exception { return t1 + t2; } }) - .subscribe(ts); + .subscribe(to); ps1.onNext(1); ps2.onNext(2); - ts.assertResult(3); + to.assertResult(3); + } + + @Test + public void firstErrorPreventsSecondSubscription() { + final AtomicInteger counter = new AtomicInteger(); + + List<Observable<?>> observableList = new ArrayList<Observable<?>>(); + observableList.add(Observable.create(new ObservableOnSubscribe<Object>() { + @Override + public void subscribe(ObservableEmitter<Object> e) + throws Exception { throw new TestException(); } + })); + observableList.add(Observable.create(new ObservableOnSubscribe<Object>() { + @Override + public void subscribe(ObservableEmitter<Object> e) + throws Exception { counter.getAndIncrement(); } + })); + + Observable.zip(observableList, + new Function<Object[], Object>() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }) + .test() + .assertFailure(TestException.class) + ; + + assertEquals(0, counter.get()); } + } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java index feb9abd77d..18f4f3be65 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleAmbTest.java @@ -16,11 +16,14 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.BiConsumer; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -33,7 +36,7 @@ public void ambWithFirstFires() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = pp1.single(-99).ambWith(pp2.single(-99)).test(); + TestObserver<Integer> to = pp1.single(-99).ambWith(pp2.single(-99)).test(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -44,7 +47,7 @@ public void ambWithFirstFires() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @@ -53,7 +56,7 @@ public void ambWithSecondFires() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = pp1.single(-99).ambWith(pp2.single(-99)).test(); + TestObserver<Integer> to = pp1.single(-99).ambWith(pp2.single(-99)).test(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -64,7 +67,7 @@ public void ambWithSecondFires() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @SuppressWarnings("unchecked") @@ -74,7 +77,7 @@ public void ambIterableWithFirstFires() { PublishProcessor<Integer> pp2 = PublishProcessor.create(); List<Single<Integer>> singles = Arrays.asList(pp1.single(-99), pp2.single(-99)); - TestObserver<Integer> ts = Single.amb(singles).test(); + TestObserver<Integer> to = Single.amb(singles).test(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -85,7 +88,7 @@ public void ambIterableWithFirstFires() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @@ -96,7 +99,7 @@ public void ambIterableWithSecondFires() { PublishProcessor<Integer> pp2 = PublishProcessor.create(); List<Single<Integer>> singles = Arrays.asList(pp1.single(-99), pp2.single(-99)); - TestObserver<Integer> ts = Single.amb(singles).test(); + TestObserver<Integer> to = Single.amb(singles).test(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -107,7 +110,7 @@ public void ambIterableWithSecondFires() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @SuppressWarnings("unchecked") @@ -134,7 +137,7 @@ public void error() { @Test public void nullSourceSuccessRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -159,7 +162,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (!errors.isEmpty()) { TestHelper.assertError(errors, 0, NullPointerException.class); @@ -173,7 +176,7 @@ public void run() { @SuppressWarnings("unchecked") @Test public void multipleErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -199,7 +202,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (!errors.isEmpty()) { TestHelper.assertUndeliverable(errors, 0, TestException.class); @@ -213,7 +216,7 @@ public void run() { @SuppressWarnings("unchecked") @Test public void successErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -240,7 +243,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (!errors.isEmpty()) { TestHelper.assertUndeliverable(errors, 0, TestException.class); @@ -281,4 +284,61 @@ public void ambArrayOrder() { Single<Integer> error = Single.error(new RuntimeException()); Single.ambArray(Single.just(1), error).test().assertValue(1); } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerSuccessDispose() throws Exception { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Single.ambArray( + Single.just(1) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Single.never() + ) + .subscribe(new BiConsumer<Object, Throwable>() { + @Override + public void accept(Object v, Throwable e) throws Exception { + assertNotNull(v); + assertNull(e); + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void noWinnerErrorDispose() throws Exception { + final TestException ex = new TestException(); + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final AtomicBoolean interrupted = new AtomicBoolean(); + final CountDownLatch cdl = new CountDownLatch(1); + + Single.ambArray( + Single.error(ex) + .subscribeOn(Schedulers.single()) + .observeOn(Schedulers.computation()), + Single.never() + ) + .subscribe(new BiConsumer<Object, Throwable>() { + @Override + public void accept(Object v, Throwable e) throws Exception { + assertNull(v); + assertNotNull(e); + interrupted.set(Thread.currentThread().isInterrupted()); + cdl.countDown(); + } + }); + + assertTrue(cdl.await(500, TimeUnit.SECONDS)); + assertFalse("Interrupted!", interrupted.get()); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleCacheTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleCacheTest.java index 47a3cabf28..ce348eebeb 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleCacheTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleCacheTest.java @@ -19,7 +19,6 @@ import io.reactivex.disposables.Disposable; import io.reactivex.observers.TestObserver; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class SingleCacheTest { @@ -29,29 +28,29 @@ public void cancelImmediately() { Single<Integer> cached = pp.single(-99).cache(); - TestObserver<Integer> ts = cached.test(true); + TestObserver<Integer> to = cached.test(true); pp.onNext(1); pp.onComplete(); - ts.assertEmpty(); + to.assertEmpty(); cached.test().assertResult(1); } @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishProcessor<Integer> pp = PublishProcessor.create(); final Single<Integer> cached = pp.single(-99).cache(); - final TestObserver<Integer> ts1 = cached.test(); + final TestObserver<Integer> to1 = cached.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; @@ -62,7 +61,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java index 8068f9e5bd..09a29e55ff 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleConcatTest.java @@ -14,9 +14,12 @@ package io.reactivex.internal.operators.single; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.Arrays; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.subscribers.TestSubscriber; import org.junit.Test; import io.reactivex.*; @@ -67,6 +70,62 @@ public void concatArray() { } } + @SuppressWarnings("unchecked") + @Test + public void concatArrayEagerTest() { + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); + + TestSubscriber<String> ts = Single.concatArrayEager(pp1.single("1"), pp2.single("2")).test(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + + pp2.onComplete(); + ts.assertEmpty(); + pp1.onComplete(); + + ts.assertResult("1", "2"); + ts.assertComplete(); + } + + @SuppressWarnings("unchecked") + @Test + public void concatEagerIterableTest() { + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); + + TestSubscriber<String> ts = Single.concatEager(Arrays.asList(pp1.single("2"), pp2.single("1"))).test(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + + pp2.onComplete(); + ts.assertEmpty(); + pp1.onComplete(); + + ts.assertResult("2", "1"); + ts.assertComplete(); + } + + @Test + public void concatEagerPublisherTest() { + PublishProcessor<String> pp1 = PublishProcessor.create(); + PublishProcessor<String> pp2 = PublishProcessor.create(); + + TestSubscriber<String> ts = Single.concatEager(Flowable.just(pp1.single("1"), pp2.single("2"))).test(); + + assertTrue(pp1.hasSubscribers()); + assertTrue(pp2.hasSubscribers()); + + pp2.onComplete(); + ts.assertEmpty(); + pp1.onComplete(); + + ts.assertResult("1", "2"); + ts.assertComplete(); + } + @SuppressWarnings("unchecked") @Test public void concatObservable() { @@ -104,7 +163,6 @@ public void subscribe(SingleEmitter<Integer> s) throws Exception { assertEquals(1, calls[0]); } - @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionIterable() { diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleCreateTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleCreateTest.java index 51603819eb..2aee2b0f6f 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleCreateTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleCreateTest.java @@ -307,4 +307,14 @@ public void subscribe(SingleEmitter<Object> e) throws Exception { RxJavaPlugins.reset(); } } + + @Test + public void emitterHasToString() { + Single.create(new SingleOnSubscribe<Object>() { + @Override + public void subscribe(SingleEmitter<Object> emitter) throws Exception { + assertTrue(emitter.toString().contains(SingleCreate.Emitter.class.getSimpleName())); + } + }).test().assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java index b5e02388fb..8f0967d8b8 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDelayTest.java @@ -13,11 +13,11 @@ package io.reactivex.internal.operators.single; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotEquals; import java.util.List; import java.util.concurrent.*; -import java.util.concurrent.atomic.*; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.reactivestreams.Subscriber; @@ -27,38 +27,63 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.schedulers.*; import io.reactivex.subjects.PublishSubject; public class SingleDelayTest { @Test - public void delay() throws Exception { - final AtomicInteger value = new AtomicInteger(); + public void delayOnSuccess() { + final TestScheduler scheduler = new TestScheduler(); + final TestObserver<Integer> observer = Single.just(1) + .delay(5, TimeUnit.SECONDS, scheduler) + .test(); - Single.just(1).delay(200, TimeUnit.MILLISECONDS) - .subscribe(new BiConsumer<Integer, Throwable>() { - @Override - public void accept(Integer v, Throwable e) throws Exception { - value.set(v); - } - }); + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + observer.assertNoValues(); + + scheduler.advanceTimeTo(5, TimeUnit.SECONDS); + observer.assertValue(1); + } - Thread.sleep(100); + @Test + public void delayOnError() { + final TestScheduler scheduler = new TestScheduler(); + final TestObserver<?> observer = Single.error(new TestException()) + .delay(5, TimeUnit.SECONDS, scheduler) + .test(); + + scheduler.triggerActions(); + observer.assertError(TestException.class); + } - assertEquals(0, value.get()); + @Test + public void delayedErrorOnSuccess() { + final TestScheduler scheduler = new TestScheduler(); + final TestObserver<Integer> observer = Single.just(1) + .delay(5, TimeUnit.SECONDS, scheduler, true) + .test(); - Thread.sleep(200); + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + observer.assertNoValues(); - assertEquals(1, value.get()); + scheduler.advanceTimeTo(5, TimeUnit.SECONDS); + observer.assertValue(1); } @Test - public void delayError() { - Single.error(new TestException()).delay(5, TimeUnit.SECONDS) - .test() - .awaitDone(1, TimeUnit.SECONDS) - .assertFailure(TestException.class); + public void delayedErrorOnError() { + final TestScheduler scheduler = new TestScheduler(); + final TestObserver<?> observer = Single.error(new TestException()) + .delay(5, TimeUnit.SECONDS, scheduler, true) + .test(); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + observer.assertNoErrors(); + + scheduler.advanceTimeTo(5, TimeUnit.SECONDS); + observer.assertError(TestException.class); } @Test @@ -188,10 +213,10 @@ public void withObservableError2() { Single.just(1) .delaySubscription(new Observable<Integer>() { @Override - protected void subscribeActual(Observer<? super Integer> s) { - s.onSubscribe(Disposables.empty()); - s.onNext(1); - s.onError(new TestException()); + protected void subscribeActual(Observer<? super Integer> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onError(new TestException()); } }) .test() @@ -220,4 +245,28 @@ public void withSingleDispose() { public void withCompletableDispose() { TestHelper.checkDisposed(Completable.complete().andThen(Single.just(1))); } + + @Test + public void withCompletableDoubleOnSubscribe() { + + TestHelper.checkDoubleOnSubscribeCompletableToSingle(new Function<Completable, Single<Object>>() { + @Override + public Single<Object> apply(Completable c) throws Exception { + return c.andThen(Single.just((Object)1)); + } + }); + + } + + @Test + public void withSingleDoubleOnSubscribe() { + + TestHelper.checkDoubleOnSubscribeSingle(new Function<Single<Object>, Single<Object>>() { + @Override + public Single<Object> apply(Single<Object> s) throws Exception { + return Single.just((Object)1).delaySubscription(s); + } + }); + + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java new file mode 100644 index 0000000000..abfbe2151a --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDematerializeTest.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.subjects.SingleSubject; + +public class SingleDematerializeTest { + + @Test + public void success() { + Single.just(Notification.createOnNext(1)) + .dematerialize(Functions.<Notification<Integer>>identity()) + .test() + .assertResult(1); + } + + @Test + public void empty() { + Single.just(Notification.<Integer>createOnComplete()) + .dematerialize(Functions.<Notification<Integer>>identity()) + .test() + .assertResult(); + } + + @Test + public void error() { + Single.<Notification<Integer>>error(new TestException()) + .dematerialize(Functions.<Notification<Integer>>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void errorNotification() { + Single.just(Notification.<Integer>createOnError(new TestException())) + .dematerialize(Functions.<Notification<Integer>>identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingleToMaybe(new Function<Single<Object>, MaybeSource<Object>>() { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public MaybeSource<Object> apply(Single<Object> v) throws Exception { + return v.dematerialize((Function)Functions.identity()); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(SingleSubject.<Notification<Integer>>create().dematerialize(Functions.<Notification<Integer>>identity())); + } + + @Test + public void selectorCrash() { + Single.just(Notification.createOnNext(1)) + .dematerialize(new Function<Notification<Integer>, Notification<Integer>>() { + @Override + public Notification<Integer> apply(Notification<Integer> v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorNull() { + Single.just(Notification.createOnNext(1)) + .dematerialize(Functions.justFunction((Notification<Integer>)null)) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void selectorDifferentType() { + Single.just(Notification.createOnNext(1)) + .dematerialize(new Function<Notification<Integer>, Notification<String>>() { + @Override + public Notification<String> apply(Notification<Integer> v) throws Exception { + return Notification.createOnNext("Value-" + 1); + } + }) + .test() + .assertResult("Value-1"); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDetachTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDetachTest.java new file mode 100644 index 0000000000..0b5c5bb3fa --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDetachTest.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.lang.ref.WeakReference; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.observers.TestObserver; +import io.reactivex.processors.PublishProcessor; + +public class SingleDetachTest { + + @Test + public void doubleSubscribe() { + + TestHelper.checkDoubleOnSubscribeSingle(new Function<Single<Object>, SingleSource<Object>>() { + @Override + public SingleSource<Object> apply(Single<Object> m) throws Exception { + return m.onTerminateDetach(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(PublishProcessor.create().singleOrError().onTerminateDetach()); + } + + @Test + public void onError() { + Single.error(new TestException()) + .onTerminateDetach() + .test() + .assertFailure(TestException.class); + } + + @Test + public void onSuccess() { + Single.just(1) + .onTerminateDetach() + .test() + .assertResult(1); + } + + @Test + public void cancelDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Object> to = new Single<Object>() { + @Override + protected void subscribeActual(SingleObserver<? super Object> observer) { + observer.onSubscribe(wr.get()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + to.cancel(); + + System.gc(); + Thread.sleep(200); + + to.assertEmpty(); + + assertNull(wr.get()); + } + + @Test + public void errorDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Integer> to = new Single<Integer>() { + @Override + protected void subscribeActual(SingleObserver<? super Integer> observer) { + observer.onSubscribe(wr.get()); + observer.onError(new TestException()); + observer.onError(new IOException()); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertFailure(TestException.class); + + assertNull(wr.get()); + } + + @Test + public void successDetaches() throws Exception { + Disposable d = Disposables.empty(); + final WeakReference<Disposable> wr = new WeakReference<Disposable>(d); + + TestObserver<Integer> to = new Single<Integer>() { + @Override + protected void subscribeActual(SingleObserver<? super Integer> observer) { + observer.onSubscribe(wr.get()); + observer.onSuccess(1); + observer.onSuccess(2); + }; + } + .onTerminateDetach() + .test(); + + d = null; + + System.gc(); + Thread.sleep(200); + + to.assertResult(1); + + assertNull(wr.get()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterSuccessTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterSuccessTest.java index 7dbe7729c6..d0e3376307 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterSuccessTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterSuccessTest.java @@ -38,7 +38,7 @@ public void accept(Integer e) throws Exception { } }; - final TestObserver<Integer> ts = new TestObserver<Integer>() { + final TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); @@ -50,7 +50,7 @@ public void onNext(Integer t) { public void just() { Single.just(1) .doAfterSuccess(afterSuccess) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -60,7 +60,7 @@ public void just() { public void error() { Single.<Integer>error(new TestException()) .doAfterSuccess(afterSuccess) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); @@ -76,7 +76,7 @@ public void justConditional() { Single.just(1) .doAfterSuccess(afterSuccess) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertEquals(Arrays.asList(1, -1), values); @@ -87,7 +87,7 @@ public void errorConditional() { Single.<Integer>error(new TestException()) .doAfterSuccess(afterSuccess) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertTrue(values.isEmpty()); diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterTerminateTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterTerminateTest.java index b1a55dc588..b7566cce30 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterTerminateTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoAfterTerminateTest.java @@ -40,13 +40,13 @@ public void run() throws Exception { } }; - private final TestObserver<Integer> ts = new TestObserver<Integer>(); + private final TestObserver<Integer> to = new TestObserver<Integer>(); @Test public void just() { Single.just(1) .doAfterTerminate(afterTerminate) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertAfterTerminateCalledOnce(); @@ -56,7 +56,7 @@ public void just() { public void error() { Single.<Integer>error(new TestException()) .doAfterTerminate(afterTerminate) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertAfterTerminateCalledOnce(); @@ -72,7 +72,7 @@ public void justConditional() { Single.just(1) .doAfterTerminate(afterTerminate) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertResult(1); assertAfterTerminateCalledOnce(); @@ -83,7 +83,7 @@ public void errorConditional() { Single.<Integer>error(new TestException()) .doAfterTerminate(afterTerminate) .filter(Functions.alwaysTrue()) - .subscribeWith(ts) + .subscribeWith(to) .assertFailure(TestException.class); assertAfterTerminateCalledOnce(); diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java new file mode 100644 index 0000000000..425ee84205 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.TestHelper; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.observers.TestObserver; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +public class SingleDoOnTerminateTest { + + @Test(expected = NullPointerException.class) + public void doOnTerminate() { + Single.just(1).doOnTerminate(null); + } + + @Test + public void doOnTerminateSuccess() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + + Single.just(1).doOnTerminate(new Action() { + @Override + public void run() throws Exception { + atomicBoolean.set(true); + } + }) + .test() + .assertResult(1); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateError() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(); + Single.error(new TestException()).doOnTerminate(new Action() { + @Override + public void run() { + atomicBoolean.set(true); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(atomicBoolean.get()); + } + + @Test + public void doOnTerminateSuccessCrash() { + Single.just(1).doOnTerminate(new Action() { + @Override + public void run() throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void doOnTerminateErrorCrash() { + TestObserver<Object> to = Single.error(new TestException("Outer")).doOnTerminate(new Action() { + @Override + public void run() { + throw new TestException("Inner"); + } + }) + .test() + .assertFailure(CompositeException.class); + + List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); + + TestHelper.assertError(errors, 0, TestException.class, "Outer"); + TestHelper.assertError(errors, 1, TestException.class, "Inner"); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTest.java index 853b21c320..ff524df77a 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTest.java @@ -94,7 +94,7 @@ public void doOnSubscribeNormal() { Single.just(1).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { count[0]++; } }) @@ -110,7 +110,7 @@ public void doOnSubscribeError() { Single.error(new TestException()).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { count[0]++; } }) @@ -125,7 +125,7 @@ public void doOnSubscribeJustCrash() { Single.just(1).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException(); } }) @@ -140,7 +140,7 @@ public void doOnSubscribeErrorCrash() { try { Single.error(new TestException("Outer")).doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException("Inner"); } }) @@ -336,15 +336,15 @@ public void onSubscribeCrash() { new Single<Integer>() { @Override - protected void subscribeActual(SingleObserver<? super Integer> s) { - s.onSubscribe(bs); - s.onError(new TestException("Second")); - s.onSuccess(1); + protected void subscribeActual(SingleObserver<? super Integer> observer) { + observer.onSubscribe(bs); + observer.onError(new TestException("Second")); + observer.onSuccess(1); } } .doOnSubscribe(new Consumer<Disposable>() { @Override - public void accept(Disposable s) throws Exception { + public void accept(Disposable d) throws Exception { throw new TestException("First"); } }) diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowableTest.java index 959c3f3ff5..1839772b67 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowableTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; -import org.reactivestreams.*; +import org.reactivestreams.Subscription; import io.reactivex.*; import io.reactivex.exceptions.TestException; @@ -108,7 +108,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { @Test public void fused() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Single.just(1).flattenAsFlowable(new Function<Integer, Iterable<Integer>>() { @Override @@ -116,17 +116,17 @@ public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(v, v + 1); } }) - .subscribe(to); + .subscribe(ts); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2); ; } @Test public void fusedNoSync() { - TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.SYNC); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); Single.just(1).flattenAsFlowable(new Function<Integer, Iterable<Integer>>() { @Override @@ -134,10 +134,10 @@ public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(v, v + 1); } }) - .subscribe(to); + .subscribe(ts); - to.assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueDisposable.NONE)) + ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2); ; } @@ -286,24 +286,24 @@ public Iterable<Integer> apply(Object v) throws Exception { return Arrays.asList(1, 2, 3); } }).subscribe(new FlowableSubscriber<Integer>() { - QueueSubscription<Integer> qd; + QueueSubscription<Integer> qs; @SuppressWarnings("unchecked") @Override - public void onSubscribe(Subscription d) { - qd = (QueueSubscription<Integer>)d; + public void onSubscribe(Subscription s) { + qs = (QueueSubscription<Integer>)s; - assertEquals(QueueSubscription.ASYNC, qd.requestFusion(QueueSubscription.ANY)); + assertEquals(QueueFuseable.ASYNC, qs.requestFusion(QueueFuseable.ANY)); } @Override public void onNext(Integer value) { - assertFalse(qd.isEmpty()); + assertFalse(qs.isEmpty()); - qd.clear(); + qs.clear(); - assertTrue(qd.isEmpty()); + assertTrue(qs.isEmpty()); - qd.cancel(); + qs.cancel(); } @Override @@ -390,7 +390,7 @@ public void requestCreateInnerRace() { final Integer[] a = new Integer[1000]; Arrays.fill(a, 1); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); ps.onNext(1); @@ -423,13 +423,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void cancelCreateInnerRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); ps.onNext(1); @@ -457,7 +457,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -554,7 +554,7 @@ public void requestIteratorRace() { final Integer[] a = new Integer[1000]; Arrays.fill(a, 1); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishSubject<Integer> ps = PublishSubject.create(); final TestSubscriber<Integer> ts = ps.singleOrError().flattenAsFlowable(new Function<Integer, Iterable<Integer>>() { @@ -581,7 +581,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservableTest.java index f1079e4669..8ae89d62d0 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservableTest.java @@ -25,7 +25,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.util.CrashingIterable; import io.reactivex.observers.*; import io.reactivex.schedulers.Schedulers; @@ -86,7 +86,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { @Test public void fused() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); Single.just(1).flattenAsObservable(new Function<Integer, Iterable<Integer>>() { @Override @@ -97,14 +97,14 @@ public Iterable<Integer> apply(Integer v) throws Exception { .subscribe(to); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2); ; } @Test public void fusedNoSync() { - TestObserver<Integer> to = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.SYNC); Single.just(1).flattenAsObservable(new Function<Integer, Iterable<Integer>>() { @Override @@ -115,7 +115,7 @@ public Iterable<Integer> apply(Integer v) throws Exception { .subscribe(to); to.assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.NONE)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertResult(1, 2); ; } @@ -295,7 +295,7 @@ public Iterable<Integer> apply(Object v) throws Exception { public void onSubscribe(Disposable d) { qd = (QueueDisposable<Integer>)d; - assertEquals(QueueDisposable.ASYNC, qd.requestFusion(QueueDisposable.ANY)); + assertEquals(QueueFuseable.ASYNC, qd.requestFusion(QueueFuseable.ANY)); } @Override diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java index 4dfb835102..ca586a0907 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFlatMapTest.java @@ -15,12 +15,15 @@ import static org.junit.Assert.*; +import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.Test; import org.reactivestreams.Publisher; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; +import io.reactivex.subscribers.TestSubscriber; public class SingleFlatMapTest { @@ -102,7 +105,6 @@ public Completable apply(Integer t) throws Exception { assertFalse(b[0]); } - @Test public void flatMapObservable() { Single.just(1).flatMapObservable(new Function<Integer, Observable<Integer>>() { @@ -127,6 +129,92 @@ public Publisher<Integer> apply(Integer v) throws Exception { .assertResult(1, 2, 3, 4, 5); } + @Test(expected = NullPointerException.class) + public void flatMapPublisherMapperNull() { + Single.just(1).flatMapPublisher(null); + } + + @Test + public void flatMapPublisherMapperThrows() { + final TestException ex = new TestException(); + Single.just(1) + .flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) throws Exception { + throw ex; + } + }) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void flatMapPublisherSingleError() { + final TestException ex = new TestException(); + Single.<Integer>error(ex) + .flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) throws Exception { + return Flowable.just(1); + } + }) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void flatMapPublisherCancelDuringSingle() { + final AtomicBoolean disposed = new AtomicBoolean(); + TestSubscriber<Integer> ts = Single.<Integer>never() + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + disposed.set(true); + } + }) + .flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) throws Exception { + return Flowable.range(v, 5); + } + }) + .test() + .assertNoValues() + .assertNotTerminated(); + assertFalse(disposed.get()); + ts.cancel(); + assertTrue(disposed.get()); + ts.assertNotTerminated(); + } + + @Test + public void flatMapPublisherCancelDuringFlowable() { + final AtomicBoolean disposed = new AtomicBoolean(); + TestSubscriber<Integer> ts = + Single.just(1) + .flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) throws Exception { + return Flowable.<Integer>never() + .doOnCancel(new Action() { + @Override + public void run() throws Exception { + disposed.set(true); + } + }); + } + }) + .test() + .assertNoValues() + .assertNotTerminated(); + assertFalse(disposed.get()); + ts.cancel(); + assertTrue(disposed.get()); + ts.assertNotTerminated(); + } + @Test(expected = NullPointerException.class) public void flatMapNull() { Single.just(1) @@ -223,4 +311,21 @@ public SingleSource<Integer> apply(Integer v) throws Exception { .test() .assertFailure(TestException.class); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingle(new Function<Single<Object>, SingleSource<Object>>() { + @Override + public SingleSource<Object> apply(Single<Object> s) + throws Exception { + return s.flatMap(new Function<Object, SingleSource<? extends Object>>() { + @Override + public SingleSource<? extends Object> apply(Object v) + throws Exception { + return Single.just(v); + } + }); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java index 6c753fafbf..23fb50c01d 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java @@ -13,11 +13,30 @@ package io.reactivex.internal.operators.single; +import io.reactivex.Observer; import io.reactivex.Single; -import java.util.concurrent.Callable; +import io.reactivex.SingleObserver; +import io.reactivex.TestHelper; +import io.reactivex.disposables.Disposable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; public class SingleFromCallableTest { + @Test public void fromCallableValue() { Single.fromCallable(new Callable<Integer>() { @@ -50,4 +69,209 @@ public void fromCallableNull() { .test() .assertFailureAndMessage(NullPointerException.class, "The callable returned a null value"); } + + @Test + public void fromCallableTwice() { + final AtomicInteger atomicInteger = new AtomicInteger(); + + Callable<Integer> callable = new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return atomicInteger.incrementAndGet(); + } + }; + + Single.fromCallable(callable) + .test() + .assertResult(1); + + assertEquals(1, atomicInteger.get()); + + Single.fromCallable(callable) + .test() + .assertResult(2); + + assertEquals(2, atomicInteger.get()); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldNotInvokeFuncUntilSubscription() throws Exception { + Callable<Object> func = mock(Callable.class); + + when(func.call()).thenReturn(new Object()); + + Single<Object> fromCallableSingle = Single.fromCallable(func); + + verifyZeroInteractions(func); + + fromCallableSingle.subscribe(); + + verify(func).call(); + } + + @Test + public void noErrorLoss() throws Exception { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final CountDownLatch cdl1 = new CountDownLatch(1); + final CountDownLatch cdl2 = new CountDownLatch(1); + + TestObserver<Integer> to = Single.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + cdl1.countDown(); + cdl2.await(5, TimeUnit.SECONDS); + return 1; + } + }).subscribeOn(Schedulers.single()).test(); + + assertTrue(cdl1.await(5, TimeUnit.SECONDS)); + + to.cancel(); + + int timeout = 10; + + while (timeout-- > 0 && errors.isEmpty()) { + Thread.sleep(100); + } + + TestHelper.assertUndeliverable(errors, 0, InterruptedException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @SuppressWarnings("unchecked") + @Test + public void shouldNotDeliverResultIfSubscriberUnsubscribedBeforeEmission() throws Exception { + Callable<String> func = mock(Callable.class); + + final CountDownLatch funcLatch = new CountDownLatch(1); + final CountDownLatch observerLatch = new CountDownLatch(1); + + when(func.call()).thenAnswer(new Answer<String>() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + observerLatch.countDown(); + + try { + funcLatch.await(); + } catch (InterruptedException e) { + // It's okay, unsubscription causes Thread interruption + + // Restoring interruption status of the Thread + Thread.currentThread().interrupt(); + } + + return "should_not_be_delivered"; + } + }); + + Single<String> fromCallableObservable = Single.fromCallable(func); + + Observer<Object> observer = TestHelper.mockObserver(); + + TestObserver<String> outer = new TestObserver<String>(observer); + + fromCallableObservable + .subscribeOn(Schedulers.computation()) + .subscribe(outer); + + // Wait until func will be invoked + observerLatch.await(); + + // Unsubscribing before emission + outer.cancel(); + + // Emitting result + funcLatch.countDown(); + + // func must be invoked + verify(func).call(); + + // Observer must not be notified at all + verify(observer).onSubscribe(any(Disposable.class)); + verifyNoMoreInteractions(observer); + } + + @Test + public void shouldAllowToThrowCheckedException() { + final Exception checkedException = new Exception("test exception"); + + Single<Object> fromCallableObservable = Single.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + throw checkedException; + } + }); + + SingleObserver<Object> observer = TestHelper.mockSingleObserver(); + + fromCallableObservable.subscribe(observer); + + verify(observer).onSubscribe(any(Disposable.class)); + verify(observer).onError(checkedException); + verifyNoMoreInteractions(observer); + } + + @Test + public void disposedOnArrival() { + final int[] count = { 0 }; + Single.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + count[0]++; + return 1; + } + }) + .test(true) + .assertEmpty(); + + assertEquals(0, count[0]); + } + + @Test + public void disposedOnCall() { + final TestObserver<Integer> to = new TestObserver<Integer>(); + + Single.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + to.cancel(); + return 1; + } + }) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void toObservableTake() { + Single.fromCallable(new Callable<Object>() { + @Override + public Object call() throws Exception { + return 1; + } + }) + .toObservable() + .take(1) + .test() + .assertResult(1); + } + + @Test + public void toObservableAndBack() { + Single.fromCallable(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return 1; + } + }) + .toObservable() + .singleOrError() + .test() + .assertResult(1); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFromPublisherTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFromPublisherTest.java index c6cc2f46d5..b7231738a6 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFromPublisherTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFromPublisherTest.java @@ -61,13 +61,13 @@ public void error() { public void dispose() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Integer> ts = Single.fromPublisher(pp).test(); + TestObserver<Integer> to = Single.fromPublisher(pp).test(); assertTrue(pp.hasSubscribers()); pp.onNext(1); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java index 5d4459259b..0a6d5473f7 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleInternalHelperTest.java @@ -21,7 +21,6 @@ import io.reactivex.*; - public class SingleInternalHelperTest { @Test diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleLiftTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleLiftTest.java index 1f09902b43..cf2345de35 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleLiftTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleLiftTest.java @@ -25,22 +25,22 @@ public void normal() { Single.just(1).lift(new SingleOperator<Integer, Integer>() { @Override - public SingleObserver<Integer> apply(final SingleObserver<? super Integer> s) throws Exception { + public SingleObserver<Integer> apply(final SingleObserver<? super Integer> observer) throws Exception { return new SingleObserver<Integer>() { @Override public void onSubscribe(Disposable d) { - s.onSubscribe(d); + observer.onSubscribe(d); } @Override public void onSuccess(Integer value) { - s.onSuccess(value + 1); + observer.onSuccess(value + 1); } @Override public void onError(Throwable e) { - s.onError(e); + observer.onError(e); } }; } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.java new file mode 100644 index 0000000000..3a97155978 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMaterializeTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; +import io.reactivex.subjects.SingleSubject; + +public class SingleMaterializeTest { + + @Test + @SuppressWarnings("unchecked") + public void success() { + Single.just(1) + .materialize() + .test() + .assertResult(Notification.createOnNext(1)); + } + + @Test + @SuppressWarnings("unchecked") + public void error() { + TestException ex = new TestException(); + Maybe.error(ex) + .materialize() + .test() + .assertResult(Notification.createOnError(ex)); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingle(new Function<Single<Object>, SingleSource<Notification<Object>>>() { + @Override + public SingleSource<Notification<Object>> apply(Single<Object> v) throws Exception { + return v.materialize(); + } + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(SingleSubject.create().materialize()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java index 8fdbff7ff1..ef5f6de826 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMergeTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.assertTrue; -import java.util.List; +import java.util.*; import org.junit.Test; @@ -70,4 +70,70 @@ public void mergeErrors() { RxJavaPlugins.reset(); } } + + @SuppressWarnings("unchecked") + @Test + public void mergeDelayErrorIterable() { + Single.mergeDelayError(Arrays.asList( + Single.just(1), + Single.<Integer>error(new TestException()), + Single.just(2)) + ) + .test() + .assertFailure(TestException.class, 1, 2); + } + + @Test + public void mergeDelayErrorPublisher() { + Single.mergeDelayError(Flowable.just( + Single.just(1), + Single.<Integer>error(new TestException()), + Single.just(2)) + ) + .test() + .assertFailure(TestException.class, 1, 2); + } + + @Test + public void mergeDelayError2() { + Single.mergeDelayError( + Single.just(1), + Single.<Integer>error(new TestException()) + ) + .test() + .assertFailure(TestException.class, 1); + } + + @Test + public void mergeDelayError2ErrorFirst() { + Single.mergeDelayError( + Single.<Integer>error(new TestException()), + Single.just(1) + ) + .test() + .assertFailure(TestException.class, 1); + } + + @Test + public void mergeDelayError3() { + Single.mergeDelayError( + Single.just(1), + Single.<Integer>error(new TestException()), + Single.just(2) + ) + .test() + .assertFailure(TestException.class, 1, 2); + } + + @Test + public void mergeDelayError4() { + Single.mergeDelayError( + Single.just(1), + Single.<Integer>error(new TestException()), + Single.just(2), + Single.just(3) + ) + .test() + .assertFailure(TestException.class, 1, 2, 3); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java index 96f92a3d9e..7b3a891680 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java @@ -27,7 +27,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; @@ -55,9 +57,9 @@ public void wrap() { Single.wrap(new SingleSource<Object>() { @Override - public void subscribe(SingleObserver<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onSuccess(1); + public void subscribe(SingleObserver<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSuccess(1); } }) .test() @@ -199,11 +201,13 @@ public boolean test(Integer i, Throwable e) throws Exception { @Test public void retryTimes() { + final AtomicInteger calls = new AtomicInteger(); + Single.fromCallable(new Callable<Object>() { - int c; + @Override public Object call() throws Exception { - if (++c != 5) { + if (calls.incrementAndGet() != 6) { throw new TestException(); } return 1; @@ -212,6 +216,8 @@ public Object call() throws Exception { .retry(5) .test() .assertResult(1); + + assertEquals(6, calls.get()); } @Test @@ -254,6 +260,7 @@ public void timeoutOther() throws Exception { } @Test + @SuppressWarnings("deprecation") public void toCompletable() { Single.just(1) .toCompletable() @@ -266,6 +273,19 @@ public void toCompletable() { .assertFailure(TestException.class); } + @Test + public void ignoreElement() { + Single.just(1) + .ignoreElement() + .test() + .assertResult(); + + Single.error(new TestException()) + .ignoreElement() + .test() + .assertFailure(TestException.class); + } + @Test public void toObservable() { Single.just(1) diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleSubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleSubscribeOnTest.java index ff669d0203..6f8b247056 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleSubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleSubscribeOnTest.java @@ -35,13 +35,13 @@ public void normal() { try { TestScheduler scheduler = new TestScheduler(); - TestObserver<Integer> ts = Single.just(1) + TestObserver<Integer> to = Single.just(1) .subscribeOn(scheduler) .test(); scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - ts.assertResult(1); + to.assertResult(1); assertTrue(list.toString(), list.isEmpty()); } finally { diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java index a253f2edb7..c7a1180a30 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleTakeUntilTest.java @@ -13,18 +13,21 @@ package io.reactivex.internal.operators.single; +import static org.junit.Assert.*; + import java.util.List; import java.util.concurrent.CancellationException; -import static org.junit.Assert.*; import org.junit.Test; +import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.*; public class SingleTakeUntilTest { @@ -33,13 +36,13 @@ public void mainSuccessPublisher() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp) + TestObserver<Integer> to = source.single(-99).takeUntil(pp) .test(); source.onNext(1); source.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -47,28 +50,27 @@ public void mainSuccessSingle() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.single(-99)) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.single(-99)) .test(); source.onNext(1); source.onComplete(); - ts.assertResult(1); + to.assertResult(1); } - @Test public void mainSuccessCompletable() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.ignoreElements()) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.ignoreElements()) .test(); source.onNext(1); source.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -76,12 +78,12 @@ public void mainErrorPublisher() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp) + TestObserver<Integer> to = source.single(-99).takeUntil(pp) .test(); source.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -89,12 +91,12 @@ public void mainErrorSingle() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.single(-99)) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.single(-99)) .test(); source.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -102,12 +104,12 @@ public void mainErrorCompletable() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.ignoreElements()) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.ignoreElements()) .test(); source.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -115,12 +117,12 @@ public void otherOnNextPublisher() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp) + TestObserver<Integer> to = source.single(-99).takeUntil(pp) .test(); pp.onNext(1); - ts.assertFailure(CancellationException.class); + to.assertFailure(CancellationException.class); } @Test @@ -128,13 +130,13 @@ public void otherOnNextSingle() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.single(-99)) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.single(-99)) .test(); pp.onNext(1); pp.onComplete(); - ts.assertFailure(CancellationException.class); + to.assertFailure(CancellationException.class); } @Test @@ -142,13 +144,13 @@ public void otherOnNextCompletable() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.ignoreElements()) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.ignoreElements()) .test(); pp.onNext(1); pp.onComplete(); - ts.assertFailure(CancellationException.class); + to.assertFailure(CancellationException.class); } @Test @@ -156,12 +158,12 @@ public void otherOnCompletePublisher() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp) + TestObserver<Integer> to = source.single(-99).takeUntil(pp) .test(); pp.onComplete(); - ts.assertFailure(CancellationException.class); + to.assertFailure(CancellationException.class); } @Test @@ -169,12 +171,12 @@ public void otherOnCompleteCompletable() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.ignoreElements()) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.ignoreElements()) .test(); pp.onComplete(); - ts.assertFailure(CancellationException.class); + to.assertFailure(CancellationException.class); } @Test @@ -182,12 +184,12 @@ public void otherErrorPublisher() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp) + TestObserver<Integer> to = source.single(-99).takeUntil(pp) .test(); pp.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -195,12 +197,12 @@ public void otherErrorSingle() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.single(-99)) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.single(-99)) .test(); pp.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -208,12 +210,12 @@ public void otherErrorCompletable() { PublishProcessor<Integer> pp = PublishProcessor.create(); PublishProcessor<Integer> source = PublishProcessor.create(); - TestObserver<Integer> ts = source.single(-99).takeUntil(pp.ignoreElements()) + TestObserver<Integer> to = source.single(-99).takeUntil(pp.ignoreElements()) .test(); pp.onError(new TestException()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test @@ -223,32 +225,32 @@ public void withPublisherDispose() { @Test public void onErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - final PublishProcessor<Integer> ps1 = PublishProcessor.create(); - final PublishProcessor<Integer> ps2 = PublishProcessor.create(); + final PublishProcessor<Integer> pp1 = PublishProcessor.create(); + final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> to = ps1.singleOrError().takeUntil(ps2).test(); + TestObserver<Integer> to = pp1.singleOrError().takeUntil(pp2).test(); final TestException ex = new TestException(); Runnable r1 = new Runnable() { @Override public void run() { - ps1.onError(ex); + pp1.onError(ex); } }; Runnable r2 = new Runnable() { @Override public void run() { - ps2.onError(ex); + pp2.onError(ex); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -274,4 +276,307 @@ public void otherSignalsAndCompletes() { RxJavaPlugins.reset(); } } + + @Test + public void flowableCancelDelayed() { + Single.never() + .takeUntil(new Flowable<Integer>() { + @Override + protected void subscribeActual(Subscriber<? super Integer> s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + } + }) + .test() + .assertFailure(CancellationException.class); + } + + @Test + public void untilSingleMainSuccess() { + SingleSubject<Integer> main = SingleSubject.create(); + SingleSubject<Integer> other = SingleSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(1); + } + + @Test + public void untilSingleMainError() { + SingleSubject<Integer> main = SingleSubject.create(); + SingleSubject<Integer> other = SingleSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilSingleOtherSuccess() { + SingleSubject<Integer> main = SingleSubject.create(); + SingleSubject<Integer> other = SingleSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(CancellationException.class); + } + + @Test + public void untilSingleOtherError() { + SingleSubject<Integer> main = SingleSubject.create(); + SingleSubject<Integer> other = SingleSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilSingleDispose() { + SingleSubject<Integer> main = SingleSubject.create(); + SingleSubject<Integer> other = SingleSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertEmpty(); + } + + @Test + public void untilPublisherMainSuccess() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + main.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertResult(1); + } + + @Test + public void untilPublisherMainError() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherOtherOnNext() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onNext(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(CancellationException.class); + } + + @Test + public void untilPublisherOtherOnComplete() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(CancellationException.class); + } + + @Test + public void untilPublisherOtherError() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilPublisherDispose() { + SingleSubject<Integer> main = SingleSubject.create(); + PublishProcessor<Integer> other = PublishProcessor.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasSubscribers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasSubscribers()); + + to.assertEmpty(); + } + + @Test + public void untilCompletableMainSuccess() { + SingleSubject<Integer> main = SingleSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onSuccess(1); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertResult(1); + } + + @Test + public void untilCompletableMainError() { + SingleSubject<Integer> main = SingleSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + main.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilCompletableOtherOnComplete() { + SingleSubject<Integer> main = SingleSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onComplete(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(CancellationException.class); + } + + @Test + public void untilCompletableOtherError() { + SingleSubject<Integer> main = SingleSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + other.onError(new TestException()); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void untilCompletableDispose() { + SingleSubject<Integer> main = SingleSubject.create(); + CompletableSubject other = CompletableSubject.create(); + + TestObserver<Integer> to = main.takeUntil(other).test(); + + assertTrue("Main no observers?", main.hasObservers()); + assertTrue("Other no observers?", other.hasObservers()); + + to.dispose(); + + assertFalse("Main has observers?", main.hasObservers()); + assertFalse("Other has observers?", other.hasObservers()); + + to.assertEmpty(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java index 0a6c590e6f..0acbc7a9ef 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleTimeoutTest.java @@ -13,17 +13,22 @@ package io.reactivex.internal.operators.single; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.Test; -import io.reactivex.Single; +import io.reactivex.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.TestScheduler; -import io.reactivex.subjects.PublishSubject; +import io.reactivex.subjects.*; public class SingleTimeoutTest { @@ -69,4 +74,151 @@ public void mainError() { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class); } + + @Test + public void disposeWhenFallback() { + TestScheduler sch = new TestScheduler(); + + SingleSubject<Integer> subj = SingleSubject.create(); + + subj.timeout(1, TimeUnit.SECONDS, sch, Single.just(1)) + .test(true) + .assertEmpty(); + + assertFalse(subj.hasObservers()); + } + + @Test + public void isDisposed() { + TestHelper.checkDisposed(SingleSubject.create().timeout(1, TimeUnit.DAYS)); + } + + @Test + public void fallbackDispose() { + TestScheduler sch = new TestScheduler(); + + SingleSubject<Integer> subj = SingleSubject.create(); + + SingleSubject<Integer> fallback = SingleSubject.create(); + + TestObserver<Integer> to = subj.timeout(1, TimeUnit.SECONDS, sch, fallback) + .test(); + + assertFalse(fallback.hasObservers()); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + assertFalse(subj.hasObservers()); + assertTrue(fallback.hasObservers()); + + to.cancel(); + + assertFalse(fallback.hasObservers()); + } + + @Test + public void normalSuccessDoesntDisposeMain() { + final int[] calls = { 0 }; + + Single.just(1) + .doOnDispose(new Action() { + @Override + public void run() throws Exception { + calls[0]++; + } + }) + .timeout(1, TimeUnit.DAYS) + .test() + .assertResult(1); + + assertEquals(0, calls[0]); + } + + @Test + public void successTimeoutRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final SingleSubject<Integer> subj = SingleSubject.create(); + SingleSubject<Integer> fallback = SingleSubject.create(); + + final TestScheduler sch = new TestScheduler(); + + TestObserver<Integer> to = subj.timeout(1, TimeUnit.MILLISECONDS, sch, fallback).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + subj.onSuccess(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.MILLISECONDS); + } + }; + + TestHelper.race(r1, r2); + + if (!fallback.hasObservers()) { + to.assertResult(1); + } else { + to.assertEmpty(); + } + } + } + + @Test + public void errorTimeoutRace() { + final TestException ex = new TestException(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final SingleSubject<Integer> subj = SingleSubject.create(); + SingleSubject<Integer> fallback = SingleSubject.create(); + + final TestScheduler sch = new TestScheduler(); + + TestObserver<Integer> to = subj.timeout(1, TimeUnit.MILLISECONDS, sch, fallback).test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + subj.onError(ex); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + sch.advanceTimeBy(1, TimeUnit.MILLISECONDS); + } + }; + + TestHelper.race(r1, r2); + + if (!fallback.hasObservers()) { + to.assertFailure(TestException.class); + } else { + to.assertEmpty(); + } + if (!errors.isEmpty()) { + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } + } + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void mainTimedOut() { + Single + .never() + .timeout(1, TimeUnit.NANOSECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailureAndMessage(TimeoutException.class, timeoutMessage(1, TimeUnit.NANOSECONDS)); + } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleTimerTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleTimerTest.java index 93719ed764..55932efb37 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleTimerTest.java @@ -38,7 +38,7 @@ public void timerInterruptible() throws Exception { try { for (Scheduler s : new Scheduler[] { Schedulers.single(), Schedulers.computation(), Schedulers.newThread(), Schedulers.io(), Schedulers.from(exec) }) { final AtomicBoolean interrupted = new AtomicBoolean(); - TestObserver<Long> ts = Single.timer(1, TimeUnit.MILLISECONDS, s) + TestObserver<Long> to = Single.timer(1, TimeUnit.MILLISECONDS, s) .map(new Function<Long, Long>() { @Override public Long apply(Long v) throws Exception { @@ -54,7 +54,7 @@ public Long apply(Long v) throws Exception { Thread.sleep(500); - ts.cancel(); + to.cancel(); Thread.sleep(500); diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleToFlowableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleToFlowableTest.java new file mode 100644 index 0000000000..3b6766e8a8 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/single/SingleToFlowableTest.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.TestHelper; +import io.reactivex.functions.Function; +import io.reactivex.subjects.PublishSubject; +import org.junit.Test; +import org.reactivestreams.Publisher; + +public class SingleToFlowableTest { + + @Test + public void dispose() { + TestHelper.checkDisposed(PublishSubject.create().singleOrError().toFlowable()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeSingleToFlowable(new Function<Single<Object>, Publisher<Object>>() { + @Override + public Publisher<Object> apply(Single<Object> s) throws Exception { + return s.toFlowable(); + } + }); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleUnsubscribeOnTest.java index 733d7d0ccc..5bcf01f1f6 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleUnsubscribeOnTest.java @@ -95,7 +95,7 @@ public SingleSource<Object> apply(Single<Object> v) throws Exception { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { PublishProcessor<Integer> pp = PublishProcessor.create(); final Disposable[] ds = { null }; @@ -124,7 +124,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleUsingTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleUsingTest.java index 5a278fbae0..f49c35144d 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleUsingTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleUsingTest.java @@ -28,7 +28,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class SingleUsingTest { @@ -102,11 +101,11 @@ public void errorNonEager() { @Test public void eagerMapperThrowsDisposerThrows() { - TestObserver<Integer> ts = Single.using(Functions.justCallable(Disposables.empty()), mapperThrows, disposerThrows) + TestObserver<Integer> to = Single.using(Functions.justCallable(Disposables.empty()), mapperThrows, disposerThrows) .test() .assertFailure(CompositeException.class); - List<Throwable> ce = TestHelper.compositeList(ts.errors().get(0)); + List<Throwable> ce = TestHelper.compositeList(to.errors().get(0)); TestHelper.assertError(ce, 0, TestException.class, "Mapper"); TestHelper.assertError(ce, 1, TestException.class, "Disposer"); } @@ -183,7 +182,7 @@ public void disposerThrowsNonEager() { @Test public void errorAndDisposerThrowsEager() { - TestObserver<Integer> ts = Single.using(Functions.justCallable(Disposables.empty()), + TestObserver<Integer> to = Single.using(Functions.justCallable(Disposables.empty()), new Function<Disposable, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Disposable v) throws Exception { @@ -193,7 +192,7 @@ public SingleSource<Integer> apply(Disposable v) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> ce = TestHelper.compositeList(ts.errors().get(0)); + List<Throwable> ce = TestHelper.compositeList(to.errors().get(0)); TestHelper.assertError(ce, 0, TestException.class, "Mapper-run"); TestHelper.assertError(ce, 1, TestException.class, "Disposer"); } @@ -220,12 +219,12 @@ public SingleSource<Integer> apply(Disposable v) throws Exception { @Test public void successDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); Disposable d = Disposables.empty(); - final TestObserver<Integer> ts = Single.using(Functions.justCallable(d), new Function<Disposable, SingleSource<Integer>>() { + final TestObserver<Integer> to = Single.using(Functions.justCallable(d), new Function<Disposable, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Disposable v) throws Exception { return pp.single(-99); @@ -244,11 +243,11 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertTrue(d.isDisposed()); } @@ -295,12 +294,12 @@ protected void subscribeActual(SingleObserver<? super Integer> observer) { @Test public void errorDisposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); Disposable d = Disposables.empty(); - final TestObserver<Integer> ts = Single.using(Functions.justCallable(d), new Function<Disposable, SingleSource<Integer>>() { + final TestObserver<Integer> to = Single.using(Functions.justCallable(d), new Function<Disposable, SingleSource<Integer>>() { @Override public SingleSource<Integer> apply(Disposable v) throws Exception { return pp.single(-99); @@ -319,11 +318,11 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertTrue(d.isDisposed()); } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java index 5c2b17c368..7d1175bfc2 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleZipArrayTest.java @@ -26,7 +26,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class SingleZipArrayTest { @@ -37,7 +36,6 @@ public Object apply(Object a, Object b) throws Exception { } }; - final Function3<Object, Object, Object, Object> addString3 = new Function3<Object, Object, Object, Object>() { @Override public Object apply(Object a, Object b, Object c) throws Exception { @@ -114,7 +112,7 @@ public void middleError() { @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -139,7 +137,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); @@ -151,6 +149,7 @@ public void run() { } } } + @SuppressWarnings("unchecked") @Test(expected = NullPointerException.class) public void zipArrayOneIsNull() { diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleZipIterableTest.java index 415aeb1db4..f5ba4954a0 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleZipIterableTest.java @@ -27,7 +27,6 @@ import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; -import io.reactivex.schedulers.Schedulers; public class SingleZipIterableTest { @@ -115,7 +114,7 @@ public void middleError() { @SuppressWarnings("unchecked") @Test public void innerErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { final PublishProcessor<Integer> pp0 = PublishProcessor.create(); @@ -141,7 +140,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); to.assertFailure(TestException.class); diff --git a/src/test/java/io/reactivex/internal/queue/SimpleQueueTest.java b/src/test/java/io/reactivex/internal/queue/SimpleQueueTest.java index dfb359d8f4..2a8583e0b4 100644 --- a/src/test/java/io/reactivex/internal/queue/SimpleQueueTest.java +++ b/src/test/java/io/reactivex/internal/queue/SimpleQueueTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.junit.Test; @@ -155,4 +155,23 @@ public void run() { t1.join(); t2.join(); } + + @Test + public void spscLinkedArrayQueueNoNepotism() { + SpscLinkedArrayQueue<Integer> q = new SpscLinkedArrayQueue<Integer>(16); + + AtomicReferenceArray<Object> ara = q.producerBuffer; + + for (int i = 0; i < 20; i++) { + q.offer(i); + } + + assertNotNull(ara.get(16)); + + for (int i = 0; i < 20; i++) { + assertEquals(i, q.poll().intValue()); + } + + assertNull(ara.get(16)); + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java b/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java index dd77428cd7..17a69ab93d 100644 --- a/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/AbstractDirectTaskTest.java @@ -115,6 +115,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { assertTrue(interrupted[0]); } + @Test public void setFutureCancelSameThread() { AbstractDirectTask task = new AbstractDirectTask(Functions.EMPTY_RUNNABLE) { @@ -208,7 +209,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Test public void disposeSetFutureRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AbstractDirectTask task = new AbstractDirectTask(Functions.EMPTY_RUNNABLE) { private static final long serialVersionUID = 208585707945686116L; }; diff --git a/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java b/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java new file mode 100644 index 0000000000..a8f61daeab --- /dev/null +++ b/src/test/java/io/reactivex/internal/schedulers/ExecutorSchedulerDelayedRunnableTest.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.schedulers; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import io.reactivex.exceptions.TestException; +import io.reactivex.internal.schedulers.ExecutorScheduler.DelayedRunnable; + +public class ExecutorSchedulerDelayedRunnableTest { + + @Test(expected = TestException.class) + public void delayedRunnableCrash() { + DelayedRunnable dl = new DelayedRunnable(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }); + dl.run(); + } + + @Test + public void dispose() { + final AtomicInteger count = new AtomicInteger(); + DelayedRunnable dl = new DelayedRunnable(new Runnable() { + @Override + public void run() { + count.incrementAndGet(); + } + }); + + dl.dispose(); + dl.dispose(); + + dl.run(); + + assertEquals(0, count.get()); + } +} diff --git a/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java b/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java index 747aa49bfe..507ebd32d0 100644 --- a/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/ImmediateThinSchedulerTest.java @@ -47,6 +47,7 @@ public void scheduleDirectTimed() { public void scheduleDirectPeriodic() { ImmediateThinScheduler.INSTANCE.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 1, 1, TimeUnit.SECONDS); } + @Test public void schedule() { final int[] count = { 0 }; diff --git a/src/test/java/io/reactivex/internal/schedulers/InstantPeriodicTaskTest.java b/src/test/java/io/reactivex/internal/schedulers/InstantPeriodicTaskTest.java new file mode 100644 index 0000000000..286be4e33a --- /dev/null +++ b/src/test/java/io/reactivex/internal/schedulers/InstantPeriodicTaskTest.java @@ -0,0 +1,277 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.reactivex.internal.schedulers; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.*; + +import org.junit.Test; + +import io.reactivex.TestHelper; +import io.reactivex.exceptions.TestException; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; + +public class InstantPeriodicTaskTest { + + @Test + public void taskCrash() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + assertNull(task.call()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void dispose() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + assertFalse(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void dispose2() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + task.setFirst(new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null)); + task.setRest(new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null)); + + assertFalse(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void dispose2CurrentThread() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + task.runner = Thread.currentThread(); + + task.setFirst(new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null)); + task.setRest(new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null)); + + assertFalse(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + + task.dispose(); + + assertTrue(task.isDisposed()); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void dispose3() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + task.dispose(); + + FutureTask<Void> f1 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + task.setFirst(f1); + + assertTrue(f1.isCancelled()); + + FutureTask<Void> f2 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + task.setRest(f2); + + assertTrue(f2.isCancelled()); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void disposeOnCurrentThread() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + + InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + task.runner = Thread.currentThread(); + + task.dispose(); + + FutureTask<Void> f1 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + task.setFirst(f1); + + assertTrue(f1.isCancelled()); + + FutureTask<Void> f2 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + task.setRest(f2); + + assertTrue(f2.isCancelled()); + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void firstCancelRace() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + final FutureTask<Void> f1 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + Runnable r1 = new Runnable() { + @Override + public void run() { + task.setFirst(f1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + task.dispose(); + } + }; + + TestHelper.race(r1, r2); + + assertTrue(f1.isCancelled()); + assertTrue(task.isDisposed()); + } + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } + + @Test + public void restCancelRace() throws Exception { + ExecutorService exec = Executors.newSingleThreadExecutor(); + try { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final InstantPeriodicTask task = new InstantPeriodicTask(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, exec); + + final FutureTask<Void> f1 = new FutureTask<Void>(Functions.EMPTY_RUNNABLE, null); + Runnable r1 = new Runnable() { + @Override + public void run() { + task.setRest(f1); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + task.dispose(); + } + }; + + TestHelper.race(r1, r2); + + assertTrue(f1.isCancelled()); + assertTrue(task.isDisposed()); + } + } finally { + exec.shutdownNow(); + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java b/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java index 169767d4c6..9d1718f7d0 100644 --- a/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/ScheduledRunnableTest.java @@ -18,6 +18,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.util.List; import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.*; import org.junit.Test; @@ -58,7 +59,7 @@ public void disposeRun() { @Test public void setFutureCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { CompositeDisposable set = new CompositeDisposable(); final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); set.add(run); @@ -87,7 +88,7 @@ public void run() { @Test public void setFutureRunRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { CompositeDisposable set = new CompositeDisposable(); final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); set.add(run); @@ -116,7 +117,7 @@ public void run() { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { CompositeDisposable set = new CompositeDisposable(); final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); set.add(run); @@ -136,7 +137,7 @@ public void run() { @Test public void runDispose() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { CompositeDisposable set = new CompositeDisposable(); final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); set.add(run); @@ -259,7 +260,7 @@ public void withFutureDisposed3() { @Test public void runFuture() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { CompositeDisposable set = new CompositeDisposable(); final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); set.add(run); @@ -283,4 +284,113 @@ public void run() { TestHelper.race(r1, r2); } } + + @Test + public void syncWorkerCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final CompositeDisposable set = new CompositeDisposable(); + final AtomicBoolean interrupted = new AtomicBoolean(); + final AtomicInteger sync = new AtomicInteger(2); + final AtomicInteger syncb = new AtomicInteger(2); + + Runnable r0 = new Runnable() { + @Override + public void run() { + set.dispose(); + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + if (syncb.decrementAndGet() != 0) { + while (syncb.get() != 0) { } + } + for (int j = 0; j < 1000; j++) { + if (Thread.currentThread().isInterrupted()) { + interrupted.set(true); + break; + } + } + } + }; + + final ScheduledRunnable run = new ScheduledRunnable(r0, set); + set.add(run); + + final FutureTask<Void> ft = new FutureTask<Void>(run, null); + + Runnable r2 = new Runnable() { + @Override + public void run() { + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + run.setFuture(ft); + if (syncb.decrementAndGet() != 0) { + while (syncb.get() != 0) { } + } + } + }; + + TestHelper.race(ft, r2); + + assertFalse("The task was interrupted", interrupted.get()); + } + } + + @Test + public void disposeAfterRun() { + final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, null); + + run.run(); + assertEquals(ScheduledRunnable.DONE, run.get(ScheduledRunnable.FUTURE_INDEX)); + + run.dispose(); + assertEquals(ScheduledRunnable.DONE, run.get(ScheduledRunnable.FUTURE_INDEX)); + } + + @Test + public void syncDisposeIdempotent() { + final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, null); + run.set(ScheduledRunnable.THREAD_INDEX, Thread.currentThread()); + + run.dispose(); + assertEquals(ScheduledRunnable.SYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + run.dispose(); + assertEquals(ScheduledRunnable.SYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + run.run(); + assertEquals(ScheduledRunnable.SYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + } + + @Test + public void asyncDisposeIdempotent() { + final ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, null); + + run.dispose(); + assertEquals(ScheduledRunnable.ASYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + run.dispose(); + assertEquals(ScheduledRunnable.ASYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + run.run(); + assertEquals(ScheduledRunnable.ASYNC_DISPOSED, run.get(ScheduledRunnable.FUTURE_INDEX)); + } + + @Test + public void noParentIsDisposed() { + ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, null); + assertFalse(run.isDisposed()); + run.run(); + assertTrue(run.isDisposed()); + } + + @Test + public void withParentIsDisposed() { + CompositeDisposable set = new CompositeDisposable(); + ScheduledRunnable run = new ScheduledRunnable(Functions.EMPTY_RUNNABLE, set); + set.add(run); + + assertFalse(run.isDisposed()); + + run.run(); + assertTrue(run.isDisposed()); + + assertFalse(set.remove(run)); + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupportTest.java b/src/test/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupportTest.java new file mode 100644 index 0000000000..23980da892 --- /dev/null +++ b/src/test/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupportTest.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.schedulers; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; + +import org.junit.Test; + +import io.reactivex.Scheduler.Worker; +import io.reactivex.TestHelper; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.internal.schedulers.SchedulerMultiWorkerSupport.WorkerCallback; +import io.reactivex.schedulers.Schedulers; + +public class SchedulerMultiWorkerSupportTest { + + final int max = ComputationScheduler.MAX_THREADS; + + @Test + public void moreThanMaxWorkers() { + final List<Worker> list = new ArrayList<Worker>(); + + SchedulerMultiWorkerSupport mws = (SchedulerMultiWorkerSupport)Schedulers.computation(); + + mws.createWorkers(max * 2, new WorkerCallback() { + @Override + public void onWorker(int i, Worker w) { + list.add(w); + } + }); + + assertEquals(max * 2, list.size()); + } + + @Test + public void getShutdownWorkers() { + final List<Worker> list = new ArrayList<Worker>(); + + ComputationScheduler.NONE.createWorkers(max * 2, new WorkerCallback() { + @Override + public void onWorker(int i, Worker w) { + list.add(w); + } + }); + + assertEquals(max * 2, list.size()); + + for (Worker w : list) { + assertEquals(ComputationScheduler.SHUTDOWN_WORKER, w); + } + } + + @Test + public void distinctThreads() throws Exception { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final CompositeDisposable composite = new CompositeDisposable(); + + try { + final CountDownLatch cdl = new CountDownLatch(max * 2); + + final Set<String> threads1 = Collections.synchronizedSet(new HashSet<String>()); + + final Set<String> threads2 = Collections.synchronizedSet(new HashSet<String>()); + + Runnable parallel1 = new Runnable() { + @Override + public void run() { + final List<Worker> list1 = new ArrayList<Worker>(); + + SchedulerMultiWorkerSupport mws = (SchedulerMultiWorkerSupport)Schedulers.computation(); + + mws.createWorkers(max, new WorkerCallback() { + @Override + public void onWorker(int i, Worker w) { + list1.add(w); + composite.add(w); + } + }); + + Runnable run = new Runnable() { + @Override + public void run() { + threads1.add(Thread.currentThread().getName()); + cdl.countDown(); + } + }; + + for (Worker w : list1) { + w.schedule(run); + } + } + }; + + Runnable parallel2 = new Runnable() { + @Override + public void run() { + final List<Worker> list2 = new ArrayList<Worker>(); + + SchedulerMultiWorkerSupport mws = (SchedulerMultiWorkerSupport)Schedulers.computation(); + + mws.createWorkers(max, new WorkerCallback() { + @Override + public void onWorker(int i, Worker w) { + list2.add(w); + composite.add(w); + } + }); + + Runnable run = new Runnable() { + @Override + public void run() { + threads2.add(Thread.currentThread().getName()); + cdl.countDown(); + } + }; + + for (Worker w : list2) { + w.schedule(run); + } + } + }; + + TestHelper.race(parallel1, parallel2); + + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + + assertEquals(threads1.toString(), max, threads1.size()); + assertEquals(threads2.toString(), max, threads2.size()); + } finally { + composite.dispose(); + } + } + } +} diff --git a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java index a8209f681d..24b3daa801 100644 --- a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java @@ -16,9 +16,14 @@ package io.reactivex.internal.schedulers; +import static org.junit.Assert.*; + import org.junit.Test; import io.reactivex.TestHelper; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; +import io.reactivex.schedulers.Schedulers; public class SchedulerPoolFactoryTest { @@ -26,4 +31,128 @@ public class SchedulerPoolFactoryTest { public void utilityClass() { TestHelper.checkUtilityClass(SchedulerPoolFactory.class); } + + @Test + public void multiStartStop() { + SchedulerPoolFactory.shutdown(); + + SchedulerPoolFactory.shutdown(); + + SchedulerPoolFactory.tryStart(false); + + assertNull(SchedulerPoolFactory.PURGE_THREAD.get()); + + SchedulerPoolFactory.start(); + + // restart schedulers + Schedulers.shutdown(); + + Schedulers.start(); + } + + @Test + public void startRace() throws InterruptedException { + try { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + SchedulerPoolFactory.shutdown(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + SchedulerPoolFactory.start(); + } + }; + + TestHelper.race(r1, r1); + } + + } finally { + // restart schedulers + Schedulers.shutdown(); + + Thread.sleep(200); + + Schedulers.start(); + } + } + + @Test + public void boolPropertiesDisabledReturnsDefaultDisabled() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(false, "key", false, true, failingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(false, "key", true, false, failingPropertiesAccessor)); + } + + @Test + public void boolPropertiesEnabledMissingReturnsDefaultMissing() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "key", true, false, missingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "key", false, true, missingPropertiesAccessor)); + } + + @Test + public void boolPropertiesFailureReturnsDefaultMissing() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "key", true, false, failingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "key", false, true, failingPropertiesAccessor)); + } + + @Test + public void boolPropertiesReturnsValue() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "true", true, false, Functions.<String>identity())); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "false", false, true, Functions.<String>identity())); + } + + @Test + public void intPropertiesDisabledReturnsDefaultDisabled() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(false, "key", 0, -1, failingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(false, "key", 1, -1, failingPropertiesAccessor)); + } + + @Test + public void intPropertiesEnabledMissingReturnsDefaultMissing() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 0, missingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 1, missingPropertiesAccessor)); + } + + @Test + public void intPropertiesFailureReturnsDefaultMissing() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 0, failingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 1, failingPropertiesAccessor)); + } + + @Test + public void intPropertiesReturnsValue() throws Throwable { + assertEquals(1, SchedulerPoolFactory.getIntProperty(true, "1", 0, 4, Functions.<String>identity())); + assertEquals(2, SchedulerPoolFactory.getIntProperty(true, "2", 3, 5, Functions.<String>identity())); + } + + static final Function<String, String> failingPropertiesAccessor = new Function<String, String>() { + @Override + public String apply(String v) throws Exception { + throw new SecurityException(); + } + }; + + static final Function<String, String> missingPropertiesAccessor = new Function<String, String>() { + @Override + public String apply(String v) throws Exception { + return null; + } + }; + + @Test + public void putIntoPoolNoPurge() { + int s = SchedulerPoolFactory.POOLS.size(); + + SchedulerPoolFactory.tryPutIntoPool(false, null); + + assertEquals(s, SchedulerPoolFactory.POOLS.size()); + } + + @Test + public void putIntoPoolNonThreadPool() { + int s = SchedulerPoolFactory.POOLS.size(); + + SchedulerPoolFactory.tryPutIntoPool(true, null); + + assertEquals(s, SchedulerPoolFactory.POOLS.size()); + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/SchedulerWhenTest.java b/src/test/java/io/reactivex/internal/schedulers/SchedulerWhenTest.java index 52ccdad85c..bbf97be11d 100644 --- a/src/test/java/io/reactivex/internal/schedulers/SchedulerWhenTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/SchedulerWhenTest.java @@ -13,20 +13,24 @@ package io.reactivex.internal.schedulers; -import static io.reactivex.Flowable.just; -import static io.reactivex.Flowable.merge; +import static io.reactivex.Flowable.*; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.*; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; -import io.reactivex.Completable; -import io.reactivex.Flowable; -import io.reactivex.Scheduler; +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.schedulers.TestScheduler; +import io.reactivex.internal.schedulers.SchedulerWhen.*; +import io.reactivex.observers.DisposableCompletableObserver; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.*; import io.reactivex.subscribers.TestSubscriber; public class SchedulerWhenTest { @@ -219,4 +223,174 @@ public Completable apply(Flowable<Flowable<Completable>> t) { merge(just(just(1).subscribeOn(limited).observeOn(comp)).repeat(1000)).blockingSubscribe(); } + + @Test + public void subscribedDisposable() { + SchedulerWhen.SUBSCRIBED.dispose(); + assertFalse(SchedulerWhen.SUBSCRIBED.isDisposed()); + } + + @Test(expected = TestException.class) + public void combineCrashInConstructor() { + new SchedulerWhen(new Function<Flowable<Flowable<Completable>>, Completable>() { + @Override + public Completable apply(Flowable<Flowable<Completable>> v) + throws Exception { + throw new TestException(); + } + }, Schedulers.single()); + } + + @Test + public void disposed() { + SchedulerWhen sw = new SchedulerWhen(new Function<Flowable<Flowable<Completable>>, Completable>() { + @Override + public Completable apply(Flowable<Flowable<Completable>> v) + throws Exception { + return Completable.never(); + } + }, Schedulers.single()); + + assertFalse(sw.isDisposed()); + + sw.dispose(); + + assertTrue(sw.isDisposed()); + } + + @Test + public void scheduledActiondisposedSetRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ScheduledAction sa = new ScheduledAction() { + + private static final long serialVersionUID = -672980251643733156L; + + @Override + protected Disposable callActual(Worker actualWorker, + CompletableObserver actionCompletable) { + return Disposables.empty(); + } + + }; + + assertFalse(sa.isDisposed()); + + Runnable r1 = new Runnable() { + @Override + public void run() { + sa.dispose(); + } + }; + + TestHelper.race(r1, r1); + + assertTrue(sa.isDisposed()); + } + } + + @Test + public void scheduledActionStates() { + final AtomicInteger count = new AtomicInteger(); + ScheduledAction sa = new ScheduledAction() { + + private static final long serialVersionUID = -672980251643733156L; + + @Override + protected Disposable callActual(Worker actualWorker, + CompletableObserver actionCompletable) { + count.incrementAndGet(); + return Disposables.empty(); + } + + }; + + assertFalse(sa.isDisposed()); + + sa.dispose(); + + assertTrue(sa.isDisposed()); + + sa.dispose(); + + assertTrue(sa.isDisposed()); + + // should not run when disposed + sa.call(Schedulers.single().createWorker(), null); + + assertEquals(0, count.get()); + + // should not run when already scheduled + sa.set(Disposables.empty()); + + sa.call(Schedulers.single().createWorker(), null); + + assertEquals(0, count.get()); + + // disposed while in call + sa = new ScheduledAction() { + + private static final long serialVersionUID = -672980251643733156L; + + @Override + protected Disposable callActual(Worker actualWorker, + CompletableObserver actionCompletable) { + count.incrementAndGet(); + dispose(); + return Disposables.empty(); + } + + }; + + sa.call(Schedulers.single().createWorker(), null); + + assertEquals(1, count.get()); + } + + @Test + public void onCompleteActionRunCrash() { + final AtomicInteger count = new AtomicInteger(); + + OnCompletedAction a = new OnCompletedAction(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + }, new DisposableCompletableObserver() { + + @Override + public void onComplete() { + count.incrementAndGet(); + } + + @Override + public void onError(Throwable e) { + count.decrementAndGet(); + e.printStackTrace(); + } + }); + + try { + a.run(); + fail("Should have thrown"); + } catch (TestException expected) { + + } + + assertEquals(1, count.get()); + } + + @Test + public void queueWorkerDispose() { + QueueWorker qw = new QueueWorker(PublishProcessor.<ScheduledAction>create(), Schedulers.single().createWorker()); + + assertFalse(qw.isDisposed()); + + qw.dispose(); + + assertTrue(qw.isDisposed()); + + qw.dispose(); + + assertTrue(qw.isDisposed()); + } } diff --git a/src/test/java/io/reactivex/internal/schedulers/SingleSchedulerTest.java b/src/test/java/io/reactivex/internal/schedulers/SingleSchedulerTest.java index 362b6dd7f1..e7b79d21bd 100644 --- a/src/test/java/io/reactivex/internal/schedulers/SingleSchedulerTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/SingleSchedulerTest.java @@ -68,7 +68,7 @@ public void run() { @Test public void startRace() { final Scheduler s = new SingleScheduler(); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { s.shutdown(); Runnable r1 = new Runnable() { diff --git a/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java b/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java index 35d5aa5568..2b76b3067a 100644 --- a/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/TrampolineSchedulerInternalTest.java @@ -25,7 +25,9 @@ import io.reactivex.Scheduler.Worker; import io.reactivex.internal.disposables.EmptyDisposable; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.TrampolineScheduler.*; import io.reactivex.schedulers.Schedulers; +import static org.mockito.Mockito.*; public class TrampolineSchedulerInternalTest { @@ -160,4 +162,50 @@ public void run() { w.dispose(); } } + + @Test + public void sleepingRunnableDisposedOnRun() { + TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, 0); + w.dispose(); + run.run(); + + verify(r, never()).run(); + } + + @Test + public void sleepingRunnableNoDelayRun() { + TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, 0); + + run.run(); + + verify(r).run(); + } + + @Test + public void sleepingRunnableDisposedOnDelayedRun() { + final TrampolineWorker w = new TrampolineWorker(); + + Runnable r = mock(Runnable.class); + + SleepingRunnable run = new SleepingRunnable(r, w, System.currentTimeMillis() + 200); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + w.dispose(); + } + }, 100, TimeUnit.MILLISECONDS); + + run.run(); + + verify(r, never()).run(); + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java index 3c35e2315d..3d6017bad4 100644 --- a/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/BlockingSubscriberTest.java @@ -93,6 +93,7 @@ public void cancelOnRequest() { public void request(long n) { bf.cancelled = true; } + @Override public void cancel() { b.set(true); @@ -118,6 +119,7 @@ public void cancelUpfront() { public void request(long n) { b.set(true); } + @Override public void cancel() { } diff --git a/src/test/java/io/reactivex/internal/subscribers/BoundedSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/BoundedSubscriberTest.java new file mode 100644 index 0000000000..17e12986b4 --- /dev/null +++ b/src/test/java/io/reactivex/internal/subscribers/BoundedSubscriberTest.java @@ -0,0 +1,388 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.subscribers; + +import io.reactivex.Flowable; +import io.reactivex.TestHelper; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import org.junit.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BoundedSubscriberTest { + + @Test + public void onSubscribeThrows() { + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object o) throws Exception { + received.add(o); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable throwable) throws Exception { + received.add(throwable); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(1); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription subscription) throws Exception { + throw new TestException(); + } + }, 128); + + assertFalse(subscriber.isDisposed()); + + Flowable.just(1).subscribe(subscriber); + + assertTrue(received.toString(), received.get(0) instanceof TestException); + assertEquals(received.toString(), 1, received.size()); + + assertTrue(subscriber.isDisposed()); + } + + @Test + public void onNextThrows() { + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object o) throws Exception { + throw new TestException(); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable throwable) throws Exception { + received.add(throwable); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(1); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription subscription) throws Exception { + subscription.request(128); + } + }, 128); + + assertFalse(subscriber.isDisposed()); + + Flowable.just(1).subscribe(subscriber); + + assertTrue(received.toString(), received.get(0) instanceof TestException); + assertEquals(received.toString(), 1, received.size()); + + assertTrue(subscriber.isDisposed()); + } + + @Test + public void onErrorThrows() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object o) throws Exception { + received.add(o); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable throwable) throws Exception { + throw new TestException("Inner"); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(1); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription subscription) throws Exception { + subscription.request(128); + } + }, 128); + + assertFalse(subscriber.isDisposed()); + + Flowable.<Integer>error(new TestException("Outer")).subscribe(subscriber); + + assertTrue(received.toString(), received.isEmpty()); + + assertTrue(subscriber.isDisposed()); + + TestHelper.assertError(errors, 0, CompositeException.class); + List<Throwable> ce = TestHelper.compositeList(errors.get(0)); + TestHelper.assertError(ce, 0, TestException.class, "Outer"); + TestHelper.assertError(ce, 1, TestException.class, "Inner"); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onCompleteThrows() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object o) throws Exception { + received.add(o); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable throwable) throws Exception { + received.add(throwable); + } + }, new Action() { + @Override + public void run() throws Exception { + throw new TestException(); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription subscription) throws Exception { + subscription.request(128); + } + }, 128); + + assertFalse(subscriber.isDisposed()); + + Flowable.<Integer>empty().subscribe(subscriber); + + assertTrue(received.toString(), received.isEmpty()); + + assertTrue(subscriber.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void onNextThrowsCancelsUpstream() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final List<Throwable> errors = new ArrayList<Throwable>(); + + BoundedSubscriber<Integer> s = new BoundedSubscriber<Integer>(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException(); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + errors.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription subscription) throws Exception { + subscription.request(128); + } + }, 128); + + pp.subscribe(s); + + assertTrue("No observers?!", pp.hasSubscribers()); + assertTrue("Has errors already?!", errors.isEmpty()); + + pp.onNext(1); + + assertFalse("Has observers?!", pp.hasSubscribers()); + assertFalse("No errors?!", errors.isEmpty()); + + assertTrue(errors.toString(), errors.get(0) instanceof TestException); + } + + @Test + public void onSubscribeThrowsCancelsUpstream() { + PublishProcessor<Integer> pp = PublishProcessor.create(); + + final List<Throwable> errors = new ArrayList<Throwable>(); + + BoundedSubscriber<Integer> s = new BoundedSubscriber<Integer>(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + errors.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + throw new TestException(); + } + }, 128); + + pp.subscribe(s); + + assertFalse("Has observers?!", pp.hasSubscribers()); + assertFalse("No errors?!", errors.isEmpty()); + + assertTrue(errors.toString(), errors.get(0) instanceof TestException); + } + + @Test + public void badSourceOnSubscribe() { + Flowable<Integer> source = Flowable.fromPublisher(new Publisher<Integer>() { + @Override + public void subscribe(Subscriber<? super Integer> s) { + BooleanSubscription s1 = new BooleanSubscription(); + s.onSubscribe(s1); + BooleanSubscription s2 = new BooleanSubscription(); + s.onSubscribe(s2); + + assertFalse(s1.isCancelled()); + assertTrue(s2.isCancelled()); + + s.onNext(1); + s.onComplete(); + } + }); + + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + received.add(v); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + received.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(100); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + s.request(128); + } + }, 128); + + source.subscribe(subscriber); + + assertEquals(Arrays.asList(1, 100), received); + } + + @Test + public void badSourceEmitAfterDone() { + Flowable<Integer> source = Flowable.fromPublisher(new Publisher<Integer>() { + @Override + public void subscribe(Subscriber<? super Integer> s) { + BooleanSubscription s1 = new BooleanSubscription(); + s.onSubscribe(s1); + + s.onNext(1); + s.onComplete(); + s.onNext(2); + s.onError(new TestException()); + s.onComplete(); + } + }); + + final List<Object> received = new ArrayList<Object>(); + + BoundedSubscriber<Object> subscriber = new BoundedSubscriber<Object>(new Consumer<Object>() { + @Override + public void accept(Object v) throws Exception { + received.add(v); + } + }, new Consumer<Throwable>() { + @Override + public void accept(Throwable e) throws Exception { + received.add(e); + } + }, new Action() { + @Override + public void run() throws Exception { + received.add(100); + } + }, new Consumer<Subscription>() { + @Override + public void accept(Subscription s) throws Exception { + s.request(128); + } + }, 128); + + source.subscribe(subscriber); + + assertEquals(Arrays.asList(1, 100), received); + } + + @Test + public void onErrorMissingShouldReportNoCustomOnError() { + BoundedSubscriber<Integer> subscriber = new BoundedSubscriber<Integer>(Functions.<Integer>emptyConsumer(), + Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION, + Functions.<Subscription>boundedConsumer(128), 128); + + assertFalse(subscriber.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + BoundedSubscriber<Integer> subscriber = new BoundedSubscriber<Integer>(Functions.<Integer>emptyConsumer(), + Functions.<Throwable>emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.<Subscription>boundedConsumer(128), 128); + + assertTrue(subscriber.hasCustomOnError()); + } +} diff --git a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java index f73fba6ea4..726828bbd7 100644 --- a/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/DeferredScalarSubscriberTest.java @@ -28,7 +28,6 @@ import io.reactivex.*; import io.reactivex.Scheduler.Worker; import io.reactivex.exceptions.TestException; -import io.reactivex.internal.subscribers.DeferredScalarSubscriber; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; @@ -107,13 +106,13 @@ public void error() { @Test public void unsubscribeComposes() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = TestSubscriber.create(0L); TestingDeferredScalarSubscriber ds = new TestingDeferredScalarSubscriber(ts); - ps.subscribe(ds); + pp.subscribe(ds); - assertTrue("No subscribers?", ps.hasSubscribers()); + assertTrue("No subscribers?", pp.hasSubscribers()); ts.cancel(); @@ -126,7 +125,7 @@ public void unsubscribeComposes() { ts.assertNoErrors(); ts.assertNotComplete(); - assertFalse("Subscribers?", ps.hasSubscribers()); + assertFalse("Subscribers?", pp.hasSubscribers()); assertTrue("Deferred not unsubscribed?", ds.isCancelled()); } @@ -231,7 +230,6 @@ public void doubleComplete() { ds.onComplete(); ds.onComplete(); - ts.assertValue(1); ts.assertNoErrors(); ts.assertComplete(); @@ -304,6 +302,7 @@ public void callsAfterUnsubscribe() { ts.assertNoErrors(); ts.assertNotComplete(); } + @Test public void emissionRequestRace() { Worker w = Schedulers.computation().createWorker(); @@ -402,8 +401,8 @@ static final class TestingDeferredScalarSubscriber extends DeferredScalarSubscri private static final long serialVersionUID = 6285096158319517837L; - TestingDeferredScalarSubscriber(Subscriber<? super Integer> actual) { - super(actual); + TestingDeferredScalarSubscriber(Subscriber<? super Integer> downstream) { + super(downstream); } @Override @@ -424,4 +423,15 @@ public void downstreamRequest(long n) { request(n); } } + + @Test + public void doubleOnSubscribe() { + TestHelper.doubleOnSubscribe(new DeferredScalarSubscriber<Integer, Integer>(new TestSubscriber<Integer>()) { + private static final long serialVersionUID = -4445381578878059054L; + + @Override + public void onNext(Integer t) { + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java index 85ace65ce0..2aa9ec7a25 100644 --- a/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/FutureSubscriberTest.java @@ -13,6 +13,7 @@ package io.reactivex.internal.subscribers; +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; import java.util.*; @@ -126,7 +127,7 @@ public void onSubscribe() throws Exception { @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FutureSubscriber<Integer> fs = new FutureSubscriber<Integer>(); Runnable r = new Runnable() { @@ -136,7 +137,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @@ -155,7 +156,7 @@ public void run() { @Test public void onErrorCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FutureSubscriber<Integer> fs = new FutureSubscriber<Integer>(); final TestException ex = new TestException(); @@ -174,13 +175,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void onCompleteCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FutureSubscriber<Integer> fs = new FutureSubscriber<Integer>(); if (i % 3 == 0) { @@ -205,7 +206,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -281,4 +282,14 @@ public void run() { assertEquals(1, fs.get().intValue()); } + + @Test + public void getTimedOut() throws Exception { + try { + fs.get(1, TimeUnit.NANOSECONDS); + fail("Should have thrown"); + } catch (TimeoutException expected) { + assertEquals(timeoutMessage(1, TimeUnit.NANOSECONDS), expected.getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java index 28058606c4..f71ad289d0 100644 --- a/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberTest.java @@ -27,12 +27,15 @@ public void requestInBatches() { @Override public void innerNext(InnerQueuedSubscriber<Integer> inner, Integer value) { } + @Override public void innerError(InnerQueuedSubscriber<Integer> inner, Throwable e) { } + @Override public void innerComplete(InnerQueuedSubscriber<Integer> inner) { } + @Override public void drain() { } @@ -47,6 +50,7 @@ public void drain() { public void request(long n) { requests.add(n); } + @Override public void cancel() { // ignore diff --git a/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java index 4666e77569..d61d1a4dfe 100644 --- a/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/LambdaSubscriberTest.java @@ -13,19 +13,20 @@ package io.reactivex.internal.subscribers; -import static org.junit.Assert.*; - -import java.util.*; - -import org.junit.Test; -import org.reactivestreams.*; - import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableInternalHelper; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; +import org.junit.Test; +import org.reactivestreams.*; + +import java.util.*; + +import static org.junit.Assert.*; public class LambdaSubscriberTest { @@ -33,7 +34,7 @@ public class LambdaSubscriberTest { public void onSubscribeThrows() { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { received.add(v); @@ -56,21 +57,21 @@ public void accept(Subscription s) throws Exception { } }); - assertFalse(o.isDisposed()); + assertFalse(subscriber.isDisposed()); - Flowable.just(1).subscribe(o); + Flowable.just(1).subscribe(subscriber); assertTrue(received.toString(), received.get(0) instanceof TestException); assertEquals(received.toString(), 1, received.size()); - assertTrue(o.isDisposed()); + assertTrue(subscriber.isDisposed()); } @Test public void onNextThrows() { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { throw new TestException(); @@ -93,14 +94,14 @@ public void accept(Subscription s) throws Exception { } }); - assertFalse(o.isDisposed()); + assertFalse(subscriber.isDisposed()); - Flowable.just(1).subscribe(o); + Flowable.just(1).subscribe(subscriber); assertTrue(received.toString(), received.get(0) instanceof TestException); assertEquals(received.toString(), 1, received.size()); - assertTrue(o.isDisposed()); + assertTrue(subscriber.isDisposed()); } @Test @@ -110,7 +111,7 @@ public void onErrorThrows() { try { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { received.add(v); @@ -133,13 +134,13 @@ public void accept(Subscription s) throws Exception { } }); - assertFalse(o.isDisposed()); + assertFalse(subscriber.isDisposed()); - Flowable.<Integer>error(new TestException("Outer")).subscribe(o); + Flowable.<Integer>error(new TestException("Outer")).subscribe(subscriber); assertTrue(received.toString(), received.isEmpty()); - assertTrue(o.isDisposed()); + assertTrue(subscriber.isDisposed()); TestHelper.assertError(errors, 0, CompositeException.class); List<Throwable> ce = TestHelper.compositeList(errors.get(0)); @@ -157,7 +158,7 @@ public void onCompleteThrows() { try { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { received.add(v); @@ -180,13 +181,13 @@ public void accept(Subscription s) throws Exception { } }); - assertFalse(o.isDisposed()); + assertFalse(subscriber.isDisposed()); - Flowable.<Integer>empty().subscribe(o); + Flowable.<Integer>empty().subscribe(subscriber); assertTrue(received.toString(), received.isEmpty()); - assertTrue(o.isDisposed()); + assertTrue(subscriber.isDisposed()); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { @@ -214,7 +215,7 @@ public void subscribe(Subscriber<? super Integer> s) { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { received.add(v); @@ -237,10 +238,11 @@ public void accept(Subscription s) throws Exception { } }); - source.subscribe(o); + source.subscribe(subscriber); assertEquals(Arrays.asList(1, 100), received); } + @Test public void badSourceEmitAfterDone() { Flowable<Integer> source = Flowable.fromPublisher(new Publisher<Integer>() { @@ -259,7 +261,7 @@ public void subscribe(Subscriber<? super Integer> s) { final List<Object> received = new ArrayList<Object>(); - LambdaSubscriber<Object> o = new LambdaSubscriber<Object>(new Consumer<Object>() { + LambdaSubscriber<Object> subscriber = new LambdaSubscriber<Object>(new Consumer<Object>() { @Override public void accept(Object v) throws Exception { received.add(v); @@ -282,18 +284,18 @@ public void accept(Subscription s) throws Exception { } }); - source.subscribe(o); + source.subscribe(subscriber); assertEquals(Arrays.asList(1, 100), received); } @Test public void onNextThrowsCancelsUpstream() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); final List<Throwable> errors = new ArrayList<Throwable>(); - ps.subscribe(new Consumer<Integer>() { + pp.subscribe(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { throw new TestException(); @@ -305,12 +307,12 @@ public void accept(Throwable e) throws Exception { } }); - assertTrue("No observers?!", ps.hasSubscribers()); + assertTrue("No observers?!", pp.hasSubscribers()); assertTrue("Has errors already?!", errors.isEmpty()); - ps.onNext(1); + pp.onNext(1); - assertFalse("Has observers?!", ps.hasSubscribers()); + assertFalse("Has observers?!", pp.hasSubscribers()); assertFalse("No errors?!", errors.isEmpty()); assertTrue(errors.toString(), errors.get(0) instanceof TestException); @@ -318,11 +320,11 @@ public void accept(Throwable e) throws Exception { @Test public void onSubscribeThrowsCancelsUpstream() { - PublishProcessor<Integer> ps = PublishProcessor.create(); + PublishProcessor<Integer> pp = PublishProcessor.create(); final List<Throwable> errors = new ArrayList<Throwable>(); - ps.subscribe(new Consumer<Integer>() { + pp.subscribe(new Consumer<Integer>() { @Override public void accept(Integer v) throws Exception { } @@ -342,9 +344,29 @@ public void accept(Subscription s) throws Exception { } }); - assertFalse("Has observers?!", ps.hasSubscribers()); + assertFalse("Has observers?!", pp.hasSubscribers()); assertFalse("No errors?!", errors.isEmpty()); assertTrue(errors.toString(), errors.get(0) instanceof TestException); } + + @Test + public void onErrorMissingShouldReportNoCustomOnError() { + LambdaSubscriber<Integer> subscriber = new LambdaSubscriber<Integer>(Functions.<Integer>emptyConsumer(), + Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION, + FlowableInternalHelper.RequestMax.INSTANCE); + + assertFalse(subscriber.hasCustomOnError()); + } + + @Test + public void customOnErrorShouldReportCustomOnError() { + LambdaSubscriber<Integer> subscriber = new LambdaSubscriber<Integer>(Functions.<Integer>emptyConsumer(), + Functions.<Throwable>emptyConsumer(), + Functions.EMPTY_ACTION, + FlowableInternalHelper.RequestMax.INSTANCE); + + assertTrue(subscriber.hasCustomOnError()); + } } diff --git a/src/test/java/io/reactivex/internal/subscribers/QueueDrainSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/QueueDrainSubscriberTest.java new file mode 100644 index 0000000000..d0360d6cd9 --- /dev/null +++ b/src/test/java/io/reactivex/internal/subscribers/QueueDrainSubscriberTest.java @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.subscribers; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.TestHelper; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.TestSubscriber; + +public class QueueDrainSubscriberTest { + + static final QueueDrainSubscriber<Integer, Integer, Integer> createUnordered(TestSubscriber<Integer> ts, final Disposable d) { + return new QueueDrainSubscriber<Integer, Integer, Integer>(ts, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathEmitMax(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + return true; + } + }; + } + + static final QueueDrainSubscriber<Integer, Integer, Integer> createOrdered(TestSubscriber<Integer> ts, final Disposable d) { + return new QueueDrainSubscriber<Integer, Integer, Integer>(ts, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathOrderedEmitMax(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + return true; + } + }; + } + + static final QueueDrainSubscriber<Integer, Integer, Integer> createUnorderedReject(TestSubscriber<Integer> ts, final Disposable d) { + return new QueueDrainSubscriber<Integer, Integer, Integer>(ts, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathEmitMax(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + return false; + } + }; + } + + static final QueueDrainSubscriber<Integer, Integer, Integer> createOrderedReject(TestSubscriber<Integer> ts, final Disposable d) { + return new QueueDrainSubscriber<Integer, Integer, Integer>(ts, new SpscArrayQueue<Integer>(4)) { + @Override + public void onNext(Integer t) { + fastPathOrderedEmitMax(t, false, d); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + super.accept(a, v); + a.onNext(v); + return false; + } + }; + } + + @Test + public void unorderedFastPathNoRequest() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnordered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.onNext(1); + + ts.assertFailure(MissingBackpressureException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void orderedFastPathNoRequest() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrdered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.onNext(1); + + ts.assertFailure(MissingBackpressureException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void acceptBadRequest() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnordered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + assertTrue(qd.accept(ts, 0)); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + qd.requested(-1); + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void unorderedFastPathRequest1() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnordered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(1); + + qd.onNext(1); + + ts.assertValuesOnly(1); + } + + @Test + public void orderedFastPathRequest1() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrdered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(1); + + qd.onNext(1); + + ts.assertValuesOnly(1); + } + + @Test + public void unorderedSlowPath() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnordered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.enter(); + qd.onNext(1); + + ts.assertEmpty(); + } + + @Test + public void orderedSlowPath() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrdered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.enter(); + qd.onNext(1); + + ts.assertEmpty(); + } + + @Test + public void orderedSlowPathNonEmptyQueue() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrdered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.queue.offer(0); + qd.requested(2); + qd.onNext(1); + + ts.assertValuesOnly(0, 1); + } + + @Test + public void unorderedOnNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + final QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnordered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(Long.MAX_VALUE); + Runnable r1 = new Runnable() { + @Override + public void run() { + qd.onNext(1); + } + }; + + TestHelper.race(r1, r1); + + ts.assertValuesOnly(1, 1); + } + } + + @Test + public void orderedOnNextRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + final QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrdered(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(Long.MAX_VALUE); + Runnable r1 = new Runnable() { + @Override + public void run() { + qd.onNext(1); + } + }; + + TestHelper.race(r1, r1); + + ts.assertValuesOnly(1, 1); + } + } + + @Test + public void unorderedFastPathReject() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createUnorderedReject(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(1); + + qd.onNext(1); + + ts.assertValuesOnly(1); + + assertEquals(1, qd.requested()); + } + + @Test + public void orderedFastPathReject() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1); + Disposable d = Disposables.empty(); + QueueDrainSubscriber<Integer, Integer, Integer> qd = createOrderedReject(ts, d); + ts.onSubscribe(new BooleanSubscription()); + + qd.requested(1); + + qd.onNext(1); + + ts.assertValuesOnly(1); + + assertEquals(1, qd.requested()); + } +} diff --git a/src/test/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriberTest.java index 6e9d2a9206..825e08d4fe 100644 --- a/src/test/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriberTest.java @@ -23,7 +23,7 @@ public class SinglePostCompleteSubscriberTest { @Test public void requestCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); final SinglePostCompleteSubscriber<Integer, Integer> spc = new SinglePostCompleteSubscriber<Integer, Integer>(ts) { diff --git a/src/test/java/io/reactivex/internal/subscribers/StrictSubscriberTest.java b/src/test/java/io/reactivex/internal/subscribers/StrictSubscriberTest.java index 0850149811..f8a3ccdb21 100644 --- a/src/test/java/io/reactivex/internal/subscribers/StrictSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/StrictSubscriberTest.java @@ -230,10 +230,10 @@ public void cancelAfterOnComplete() { final List<Object> list = new ArrayList<Object>(); Subscriber<Object> sub = new Subscriber<Object>() { - Subscription s; + Subscription upstream; @Override public void onSubscribe(Subscription s) { - this.s = s; + this.upstream = s; } @Override @@ -243,13 +243,13 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - s.cancel(); + upstream.cancel(); list.add(t); } @Override public void onComplete() { - s.cancel(); + upstream.cancel(); list.add("Done"); } }; @@ -272,10 +272,10 @@ public void cancelAfterOnError() { final List<Object> list = new ArrayList<Object>(); Subscriber<Object> sub = new Subscriber<Object>() { - Subscription s; + Subscription upstream; @Override public void onSubscribe(Subscription s) { - this.s = s; + this.upstream = s; } @Override @@ -285,13 +285,13 @@ public void onNext(Object t) { @Override public void onError(Throwable t) { - s.cancel(); + upstream.cancel(); list.add(t.getMessage()); } @Override public void onComplete() { - s.cancel(); + upstream.cancel(); list.add("Done"); } }; diff --git a/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java b/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java index d5a5fc6ac1..6841d5bffd 100644 --- a/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java +++ b/src/test/java/io/reactivex/internal/subscribers/SubscriberResourceWrapperTest.java @@ -16,9 +16,12 @@ import static org.junit.Assert.*; import org.junit.Test; +import org.reactivestreams.Subscriber; +import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Function; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.subscribers.TestSubscriber; @@ -80,4 +83,31 @@ public void complete() { ts.assertResult(); } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { + @Override + public Flowable<Object> apply(Flowable<Object> f) throws Exception { + return f.lift(new FlowableOperator<Object, Object>() { + @Override + public Subscriber<? super Object> apply( + Subscriber<? super Object> s) throws Exception { + return new SubscriberResourceWrapper<Object>(s); + } + }); + } + }); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.never().lift(new FlowableOperator<Object, Object>() { + @Override + public Subscriber<? super Object> apply( + Subscriber<? super Object> s) throws Exception { + return new SubscriberResourceWrapper<Object>(s); + } + })); + } } diff --git a/src/test/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscriptionTest.java b/src/test/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscriptionTest.java index 68997ebe64..6861f9cb0e 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscriptionTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscriptionTest.java @@ -18,7 +18,6 @@ import org.junit.Test; import io.reactivex.TestHelper; -import io.reactivex.schedulers.Schedulers; public class ArrayCompositeSubscriptionTest { @@ -94,7 +93,7 @@ public void replace() { @Test public void disposeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ArrayCompositeSubscription ac = new ArrayCompositeSubscription(1000); Runnable r = new Runnable() { @@ -104,13 +103,13 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @Test public void setReplaceRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ArrayCompositeSubscription ac = new ArrayCompositeSubscription(1); final BooleanSubscription s1 = new BooleanSubscription(); @@ -130,7 +129,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/subscriptions/DeferredScalarSubscriptionTest.java b/src/test/java/io/reactivex/internal/subscriptions/DeferredScalarSubscriptionTest.java index d090760961..430e3c71c5 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/DeferredScalarSubscriptionTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/DeferredScalarSubscriptionTest.java @@ -18,8 +18,7 @@ import org.junit.Test; import io.reactivex.TestHelper; -import io.reactivex.internal.fuseable.QueueSubscription; -import io.reactivex.schedulers.Schedulers; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.subscribers.TestSubscriber; public class DeferredScalarSubscriptionTest { @@ -28,7 +27,7 @@ public class DeferredScalarSubscriptionTest { public void queueSubscriptionSyncRejected() { DeferredScalarSubscription<Integer> ds = new DeferredScalarSubscription<Integer>(new TestSubscriber<Integer>()); - assertEquals(QueueSubscription.NONE, ds.requestFusion(QueueSubscription.SYNC)); + assertEquals(QueueFuseable.NONE, ds.requestFusion(QueueFuseable.SYNC)); } @Test @@ -54,7 +53,7 @@ public void cancel() { @Test public void completeCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final DeferredScalarSubscription<Integer> ds = new DeferredScalarSubscription<Integer>(new TestSubscriber<Integer>()); Runnable r1 = new Runnable() { @@ -71,13 +70,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void requestClearRace() { - for (int i = 0; i < 5000; i++) { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); final DeferredScalarSubscription<Integer> ds = new DeferredScalarSubscription<Integer>(ts); @@ -98,7 +97,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (ts.valueCount() >= 1) { ts.assertValue(1); @@ -108,7 +107,7 @@ public void run() { @Test public void requestCancelRace() { - for (int i = 0; i < 5000; i++) { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); final DeferredScalarSubscription<Integer> ds = new DeferredScalarSubscription<Integer>(ts); @@ -129,7 +128,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (ts.valueCount() >= 1) { ts.assertValue(1); diff --git a/src/test/java/io/reactivex/internal/subscriptions/FullArbiterTest.java b/src/test/java/io/reactivex/internal/subscriptions/FullArbiterTest.java deleted file mode 100644 index e5dd5d20ce..0000000000 --- a/src/test/java/io/reactivex/internal/subscriptions/FullArbiterTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.internal.subscriptions; - -import static org.junit.Assert.*; - -import java.util.List; - -import org.junit.Test; - -import io.reactivex.TestHelper; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.util.NotificationLite; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subscribers.TestSubscriber; - -public class FullArbiterTest { - - @Test - public void initialRequested() { - FullArbiter.INITIAL.request(-99); - } - - @Test - public void initialCancel() { - FullArbiter.INITIAL.cancel(); - } - - @Test - public void invalidDeferredRequest() { - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - new FullArbiter<Integer>(new TestSubscriber<Integer>(), null, 128).request(-99); - - TestHelper.assertError(errors, 0, IllegalArgumentException.class, "n > 0 required but it was -99"); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void setSubscriptionAfterCancel() { - FullArbiter<Integer> fa = new FullArbiter<Integer>(new TestSubscriber<Integer>(), null, 128); - - fa.cancel(); - - BooleanSubscription bs = new BooleanSubscription(); - - assertFalse(fa.setSubscription(bs)); - - assertFalse(fa.setSubscription(null)); - } - - @Test - public void cancelAfterPoll() { - FullArbiter<Integer> fa = new FullArbiter<Integer>(new TestSubscriber<Integer>(), null, 128); - - BooleanSubscription bs = new BooleanSubscription(); - - fa.queue.offer(fa.s, NotificationLite.subscription(bs)); - - fa.cancel(); - - fa.drain(); - - assertTrue(bs.isCancelled()); - } - - @Test - public void errorAfterCancel() { - FullArbiter<Integer> fa = new FullArbiter<Integer>(new TestSubscriber<Integer>(), null, 128); - - BooleanSubscription bs = new BooleanSubscription(); - - fa.cancel(); - - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - fa.onError(new TestException(), bs); - - TestHelper.assertUndeliverable(errors, 0, TestException.class); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void cancelAfterError() { - FullArbiter<Integer> fa = new FullArbiter<Integer>(new TestSubscriber<Integer>(), null, 128); - - List<Throwable> errors = TestHelper.trackPluginErrors(); - try { - fa.queue.offer(fa.s, NotificationLite.error(new TestException())); - - fa.cancel(); - - fa.drain(); - TestHelper.assertUndeliverable(errors, 0, TestException.class); - } finally { - RxJavaPlugins.reset(); - } - } - - @Test - public void offerDifferentSubscription() { - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - - FullArbiter<Integer> fa = new FullArbiter<Integer>(ts, null, 128); - - BooleanSubscription bs = new BooleanSubscription(); - - fa.queue.offer(bs, NotificationLite.next(1)); - - fa.drain(); - - ts.assertNoValues(); - } -} diff --git a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java index 02d7d30c0a..426d1779e1 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionArbiterTest.java @@ -26,7 +26,7 @@ public class SubscriptionArbiterTest { @Test public void setSubscriptionMissed() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -45,7 +45,7 @@ public void setSubscriptionMissed() { @Test public void invalidDeferredRequest() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); List<Throwable> errors = TestHelper.trackPluginErrors(); try { @@ -59,7 +59,7 @@ public void invalidDeferredRequest() { @Test public void unbounded() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.request(Long.MAX_VALUE); @@ -86,7 +86,7 @@ public void unbounded() { @Test public void cancelled() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.cancelled = true; BooleanSubscription bs1 = new BooleanSubscription(); @@ -102,7 +102,7 @@ public void cancelled() { @Test public void drainUnbounded() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -113,7 +113,7 @@ public void drainUnbounded() { @Test public void drainMissedRequested() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -128,7 +128,7 @@ public void drainMissedRequested() { @Test public void drainMissedRequestedProduced() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -147,7 +147,7 @@ public void drainMissedRequestedProduced() { public void drainMissedRequestedMoreProduced() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -169,7 +169,7 @@ public void drainMissedRequestedMoreProduced() { @Test public void missedSubscriptionNoPrior() { - SubscriptionArbiter sa = new SubscriptionArbiter(); + SubscriptionArbiter sa = new SubscriptionArbiter(true); sa.getAndIncrement(); @@ -181,4 +181,130 @@ public void missedSubscriptionNoPrior() { assertSame(bs1, sa.actual); } + + @Test + public void noCancelFastPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + sa.setSubscription(bs2); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void cancelFastPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + sa.setSubscription(bs2); + + assertTrue(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void noCancelSlowPathReplace() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + BooleanSubscription bs3 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + sa.setSubscription(bs3); + + sa.drainLoop(); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + assertFalse(bs3.isCancelled()); + } + + @Test + public void cancelSlowPathReplace() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + BooleanSubscription bs3 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + sa.setSubscription(bs3); + + sa.drainLoop(); + + assertTrue(bs1.isCancelled()); + assertTrue(bs2.isCancelled()); + assertFalse(bs3.isCancelled()); + } + + @Test + public void noCancelSlowPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + + sa.drainLoop(); + + assertFalse(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void cancelSlowPath() { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + BooleanSubscription bs1 = new BooleanSubscription(); + BooleanSubscription bs2 = new BooleanSubscription(); + + sa.setSubscription(bs1); + + sa.getAndIncrement(); + + sa.setSubscription(bs2); + + sa.drainLoop(); + + assertTrue(bs1.isCancelled()); + assertFalse(bs2.isCancelled()); + } + + @Test + public void moreProducedViolationFastPath() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + SubscriptionArbiter sa = new SubscriptionArbiter(true); + + sa.produced(2); + + assertEquals(0, sa.requested); + + TestHelper.assertError(errors, 0, IllegalStateException.class, "More produced than requested: -2"); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java index a84289670f..dd010ca100 100644 --- a/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java +++ b/src/test/java/io/reactivex/internal/subscriptions/SubscriptionHelperTest.java @@ -14,7 +14,7 @@ package io.reactivex.internal.subscriptions; import static org.junit.Assert.*; - +import static org.mockito.Mockito.*; import java.util.List; import java.util.concurrent.atomic.*; @@ -22,8 +22,8 @@ import org.reactivestreams.Subscription; import io.reactivex.TestHelper; +import io.reactivex.exceptions.ProtocolViolationException; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class SubscriptionHelperTest { @@ -51,15 +51,15 @@ public void cancelNoOp() { @Test public void set() { - AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); BooleanSubscription bs1 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.set(s, bs1)); + assertTrue(SubscriptionHelper.set(atomicSubscription, bs1)); BooleanSubscription bs2 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.set(s, bs2)); + assertTrue(SubscriptionHelper.set(atomicSubscription, bs2)); assertTrue(bs1.isCancelled()); @@ -68,15 +68,15 @@ public void set() { @Test public void replace() { - AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); BooleanSubscription bs1 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.replace(s, bs1)); + assertTrue(SubscriptionHelper.replace(atomicSubscription, bs1)); BooleanSubscription bs2 = new BooleanSubscription(); - assertTrue(SubscriptionHelper.replace(s, bs2)); + assertTrue(SubscriptionHelper.replace(atomicSubscription, bs2)); assertFalse(bs1.isCancelled()); @@ -85,24 +85,24 @@ public void replace() { @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); Runnable r = new Runnable() { @Override public void run() { - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(atomicSubscription); } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } @Test public void setRace() { - for (int i = 0; i < 500; i++) { - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); final BooleanSubscription bs1 = new BooleanSubscription(); final BooleanSubscription bs2 = new BooleanSubscription(); @@ -110,18 +110,18 @@ public void setRace() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.set(s, bs1); + SubscriptionHelper.set(atomicSubscription, bs1); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.set(s, bs2); + SubscriptionHelper.set(atomicSubscription, bs2); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertTrue(bs1.isCancelled() ^ bs2.isCancelled()); } @@ -129,8 +129,8 @@ public void run() { @Test public void replaceRace() { - for (int i = 0; i < 500; i++) { - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); final BooleanSubscription bs1 = new BooleanSubscription(); final BooleanSubscription bs2 = new BooleanSubscription(); @@ -138,18 +138,18 @@ public void replaceRace() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.replace(s, bs1); + SubscriptionHelper.replace(atomicSubscription, bs1); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.replace(s, bs2); + SubscriptionHelper.replace(atomicSubscription, bs2); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertFalse(bs1.isCancelled()); assertFalse(bs2.isCancelled()); @@ -158,31 +158,31 @@ public void run() { @Test public void cancelAndChange() { - AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); - SubscriptionHelper.cancel(s); + SubscriptionHelper.cancel(atomicSubscription); BooleanSubscription bs1 = new BooleanSubscription(); - assertFalse(SubscriptionHelper.set(s, bs1)); + assertFalse(SubscriptionHelper.set(atomicSubscription, bs1)); assertTrue(bs1.isCancelled()); - assertFalse(SubscriptionHelper.set(s, null)); + assertFalse(SubscriptionHelper.set(atomicSubscription, null)); BooleanSubscription bs2 = new BooleanSubscription(); - assertFalse(SubscriptionHelper.replace(s, bs2)); + assertFalse(SubscriptionHelper.replace(atomicSubscription, bs2)); assertTrue(bs2.isCancelled()); - assertFalse(SubscriptionHelper.replace(s, null)); + assertFalse(SubscriptionHelper.replace(atomicSubscription, null)); } @Test public void invalidDeferredRequest() { - AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); AtomicLong r = new AtomicLong(); List<Throwable> errors = TestHelper.trackPluginErrors(); try { - SubscriptionHelper.deferredRequest(s, r, -99); + SubscriptionHelper.deferredRequest(atomicSubscription, r, -99); TestHelper.assertError(errors, 0, IllegalArgumentException.class, "n > 0 required but it was -99"); } finally { @@ -192,8 +192,8 @@ public void invalidDeferredRequest() { @Test public void deferredRace() { - for (int i = 0; i < 500; i++) { - final AtomicReference<Subscription> s = new AtomicReference<Subscription>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final AtomicReference<Subscription> atomicSubscription = new AtomicReference<Subscription>(); final AtomicLong r = new AtomicLong(); final AtomicLong q = new AtomicLong(); @@ -213,22 +213,48 @@ public void cancel() { Runnable r1 = new Runnable() { @Override public void run() { - SubscriptionHelper.deferredSetOnce(s, r, a); + SubscriptionHelper.deferredSetOnce(atomicSubscription, r, a); } }; Runnable r2 = new Runnable() { @Override public void run() { - SubscriptionHelper.deferredRequest(s, r, 1); + SubscriptionHelper.deferredRequest(atomicSubscription, r, 1); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - assertSame(a, s.get()); + assertSame(a, atomicSubscription.get()); assertEquals(1, q.get()); assertEquals(0, r.get()); } } + + @Test + public void setOnceAndRequest() { + AtomicReference<Subscription> ref = new AtomicReference<Subscription>(); + + Subscription sub = mock(Subscription.class); + + assertTrue(SubscriptionHelper.setOnce(ref, sub, 1)); + + verify(sub).request(1); + verify(sub, never()).cancel(); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + sub = mock(Subscription.class); + + assertFalse(SubscriptionHelper.setOnce(ref, sub, 1)); + + verify(sub, never()).request(anyLong()); + verify(sub).cancel(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } + } } diff --git a/src/test/java/io/reactivex/internal/util/BackpressureHelperTest.java b/src/test/java/io/reactivex/internal/util/BackpressureHelperTest.java index a9796f08c5..fbcff0ab95 100644 --- a/src/test/java/io/reactivex/internal/util/BackpressureHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/BackpressureHelperTest.java @@ -24,7 +24,6 @@ import io.reactivex.TestHelper; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class BackpressureHelperTest { @Ignore("BackpressureHelper is an enum") @@ -85,7 +84,7 @@ public void producedMoreCancel() { public void requestProduceRace() { final AtomicLong requested = new AtomicLong(1); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { Runnable r1 = new Runnable() { @Override @@ -101,7 +100,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -109,7 +108,7 @@ public void run() { public void requestCancelProduceRace() { final AtomicLong requested = new AtomicLong(1); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { Runnable r1 = new Runnable() { @Override @@ -125,7 +124,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } diff --git a/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java b/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java index fa07598d30..13a672f566 100644 --- a/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/EndConsumerHelperTest.java @@ -54,9 +54,11 @@ public void checkDoubleDefaultSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -85,9 +87,11 @@ static final class EndDefaultSubscriber extends DefaultSubscriber<Integer> { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -124,9 +128,11 @@ public void checkDoubleDisposableSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -157,9 +163,11 @@ public void checkDoubleResourceSubscriber() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -190,9 +198,11 @@ public void checkDoubleDefaultObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -223,9 +233,11 @@ public void checkDoubleDisposableObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -256,9 +268,11 @@ public void checkDoubleResourceObserver() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -289,6 +303,7 @@ public void checkDoubleDisposableSingleObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } @@ -319,6 +334,7 @@ public void checkDoubleResourceSingleObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } @@ -349,9 +365,11 @@ public void checkDoubleDisposableMaybeObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -382,9 +400,11 @@ public void checkDoubleResourceMaybeObserver() { @Override public void onSuccess(Integer t) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -415,6 +435,7 @@ public void checkDoubleDisposableCompletableObserver() { @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -445,6 +466,7 @@ public void checkDoubleResourceCompletableObserver() { @Override public void onError(Throwable t) { } + @Override public void onComplete() { } @@ -482,11 +504,11 @@ public void validateDisposable() { @Test public void validateSubscription() { - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - assertFalse(EndConsumerHelper.validate(SubscriptionHelper.CANCELLED, d1, getClass())); + assertFalse(EndConsumerHelper.validate(SubscriptionHelper.CANCELLED, bs1, getClass())); - assertTrue(d1.isCancelled()); + assertTrue(bs1.isCancelled()); assertTrue(errors.toString(), errors.isEmpty()); } diff --git a/src/test/java/io/reactivex/internal/util/ExceptionHelperTest.java b/src/test/java/io/reactivex/internal/util/ExceptionHelperTest.java index 618ae20d31..de401c0df7 100644 --- a/src/test/java/io/reactivex/internal/util/ExceptionHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/ExceptionHelperTest.java @@ -13,14 +13,14 @@ package io.reactivex.internal.util; +import static org.junit.Assert.assertTrue; + import java.util.concurrent.atomic.AtomicReference; -import static org.junit.Assert.*; import org.junit.Test; import io.reactivex.TestHelper; import io.reactivex.exceptions.TestException; -import io.reactivex.schedulers.Schedulers; public class ExceptionHelperTest { @Test @@ -30,7 +30,7 @@ public void utilityClass() { @Test public void addRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); @@ -43,7 +43,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); } } diff --git a/src/test/java/io/reactivex/internal/util/HalfSerializerObserverTest.java b/src/test/java/io/reactivex/internal/util/HalfSerializerObserverTest.java index 2b923b6fb0..dcf4981856 100644 --- a/src/test/java/io/reactivex/internal/util/HalfSerializerObserverTest.java +++ b/src/test/java/io/reactivex/internal/util/HalfSerializerObserverTest.java @@ -23,7 +23,6 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.observers.TestObserver; -import io.reactivex.schedulers.Schedulers; public class HalfSerializerObserverTest { @@ -35,12 +34,12 @@ public void reentrantOnNextOnNext() { final Observer[] a = { null }; - final TestObserver ts = new TestObserver(); + final TestObserver to = new TestObserver(); - Observer s = new Observer() { + Observer observer = new Observer() { @Override - public void onSubscribe(Disposable s) { - ts.onSubscribe(s); + public void onSubscribe(Disposable d) { + to.onSubscribe(d); } @Override @@ -48,27 +47,27 @@ public void onNext(Object t) { if (t.equals(1)) { HalfSerializer.onNext(a[0], 2, wip, error); } - ts.onNext(t); + to.onNext(t); } @Override public void onError(Throwable t) { - ts.onError(t); + to.onError(t); } @Override public void onComplete() { - ts.onComplete(); + to.onComplete(); } }; - a[0] = s; + a[0] = observer; - s.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); - HalfSerializer.onNext(s, 1, wip, error); + HalfSerializer.onNext(observer, 1, wip, error); - ts.assertValue(1).assertNoErrors().assertNotComplete(); + to.assertValue(1).assertNoErrors().assertNotComplete(); } @Test @@ -79,12 +78,12 @@ public void reentrantOnNextOnError() { final Observer[] a = { null }; - final TestObserver ts = new TestObserver(); + final TestObserver to = new TestObserver(); - Observer s = new Observer() { + Observer observer = new Observer() { @Override - public void onSubscribe(Disposable s) { - ts.onSubscribe(s); + public void onSubscribe(Disposable d) { + to.onSubscribe(d); } @Override @@ -92,27 +91,27 @@ public void onNext(Object t) { if (t.equals(1)) { HalfSerializer.onError(a[0], new TestException(), wip, error); } - ts.onNext(t); + to.onNext(t); } @Override public void onError(Throwable t) { - ts.onError(t); + to.onError(t); } @Override public void onComplete() { - ts.onComplete(); + to.onComplete(); } }; - a[0] = s; + a[0] = observer; - s.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); - HalfSerializer.onNext(s, 1, wip, error); + HalfSerializer.onNext(observer, 1, wip, error); - ts.assertFailure(TestException.class, 1); + to.assertFailure(TestException.class, 1); } @Test @@ -123,12 +122,12 @@ public void reentrantOnNextOnComplete() { final Observer[] a = { null }; - final TestObserver ts = new TestObserver(); + final TestObserver to = new TestObserver(); - Observer s = new Observer() { + Observer observer = new Observer() { @Override - public void onSubscribe(Disposable s) { - ts.onSubscribe(s); + public void onSubscribe(Disposable d) { + to.onSubscribe(d); } @Override @@ -136,27 +135,27 @@ public void onNext(Object t) { if (t.equals(1)) { HalfSerializer.onComplete(a[0], wip, error); } - ts.onNext(t); + to.onNext(t); } @Override public void onError(Throwable t) { - ts.onError(t); + to.onError(t); } @Override public void onComplete() { - ts.onComplete(); + to.onComplete(); } }; - a[0] = s; + a[0] = observer; - s.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); - HalfSerializer.onNext(s, 1, wip, error); + HalfSerializer.onNext(observer, 1, wip, error); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -167,105 +166,105 @@ public void reentrantErrorOnError() { final Observer[] a = { null }; - final TestObserver ts = new TestObserver(); + final TestObserver to = new TestObserver(); - Observer s = new Observer() { + Observer observer = new Observer() { @Override - public void onSubscribe(Disposable s) { - ts.onSubscribe(s); + public void onSubscribe(Disposable d) { + to.onSubscribe(d); } @Override public void onNext(Object t) { - ts.onNext(t); + to.onNext(t); } @Override public void onError(Throwable t) { - ts.onError(t); + to.onError(t); HalfSerializer.onError(a[0], new IOException(), wip, error); } @Override public void onComplete() { - ts.onComplete(); + to.onComplete(); } }; - a[0] = s; + a[0] = observer; - s.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); - HalfSerializer.onError(s, new TestException(), wip, error); + HalfSerializer.onError(observer, new TestException(), wip, error); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicInteger wip = new AtomicInteger(); final AtomicThrowable error = new AtomicThrowable(); - final TestObserver<Integer> ts = new TestObserver<Integer>(); - ts.onSubscribe(Disposables.empty()); + final TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); Runnable r1 = new Runnable() { @Override public void run() { - HalfSerializer.onNext(ts, 1, wip, error); + HalfSerializer.onNext(to, 1, wip, error); } }; Runnable r2 = new Runnable() { @Override public void run() { - HalfSerializer.onComplete(ts, wip, error); + HalfSerializer.onComplete(to, wip, error); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertComplete().assertNoErrors(); + to.assertComplete().assertNoErrors(); - assertTrue(ts.valueCount() <= 1); + assertTrue(to.valueCount() <= 1); } } @Test public void onErrorOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicInteger wip = new AtomicInteger(); final AtomicThrowable error = new AtomicThrowable(); - final TestObserver<Integer> ts = new TestObserver<Integer>(); + final TestObserver<Integer> to = new TestObserver<Integer>(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); final TestException ex = new TestException(); Runnable r1 = new Runnable() { @Override public void run() { - HalfSerializer.onError(ts, ex, wip, error); + HalfSerializer.onError(to, ex, wip, error); } }; Runnable r2 = new Runnable() { @Override public void run() { - HalfSerializer.onComplete(ts, wip, error); + HalfSerializer.onComplete(to, wip, error); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - if (ts.completions() != 0) { - ts.assertResult(); + if (to.completions() != 0) { + to.assertResult(); } else { - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } } } diff --git a/src/test/java/io/reactivex/internal/util/HalfSerializerSubscriberTest.java b/src/test/java/io/reactivex/internal/util/HalfSerializerSubscriberTest.java index 4a3adf662f..4cfbf4bb91 100644 --- a/src/test/java/io/reactivex/internal/util/HalfSerializerSubscriberTest.java +++ b/src/test/java/io/reactivex/internal/util/HalfSerializerSubscriberTest.java @@ -23,7 +23,6 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class HalfSerializerSubscriberTest { @@ -209,7 +208,7 @@ public void onComplete() { @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicInteger wip = new AtomicInteger(); final AtomicThrowable error = new AtomicThrowable(); @@ -231,7 +230,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertComplete().assertNoErrors(); @@ -241,7 +240,7 @@ public void run() { @Test public void onErrorOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AtomicInteger wip = new AtomicInteger(); final AtomicThrowable error = new AtomicThrowable(); @@ -266,7 +265,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (ts.completions() != 0) { ts.assertResult(); diff --git a/src/test/java/io/reactivex/internal/util/MergerBiFunctionTest.java b/src/test/java/io/reactivex/internal/util/MergerBiFunctionTest.java new file mode 100644 index 0000000000..db84d4cf1a --- /dev/null +++ b/src/test/java/io/reactivex/internal/util/MergerBiFunctionTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.internal.util; + +import static org.junit.Assert.assertEquals; + +import java.util.*; + +import org.junit.Test; + +public class MergerBiFunctionTest { + + @Test + public void firstEmpty() throws Exception { + MergerBiFunction<Integer> merger = new MergerBiFunction<Integer>(new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + List<Integer> list = merger.apply(Collections.<Integer>emptyList(), Arrays.asList(3, 5)); + + assertEquals(Arrays.asList(3, 5), list); + } + + @Test + public void bothEmpty() throws Exception { + MergerBiFunction<Integer> merger = new MergerBiFunction<Integer>(new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + List<Integer> list = merger.apply(Collections.<Integer>emptyList(), Collections.<Integer>emptyList()); + + assertEquals(Collections.<Integer>emptyList(), list); + } + + @Test + public void secondEmpty() throws Exception { + MergerBiFunction<Integer> merger = new MergerBiFunction<Integer>(new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + List<Integer> list = merger.apply(Arrays.asList(2, 4), Collections.<Integer>emptyList()); + + assertEquals(Arrays.asList(2, 4), list); + } + + @Test + public void sameSize() throws Exception { + MergerBiFunction<Integer> merger = new MergerBiFunction<Integer>(new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + List<Integer> list = merger.apply(Arrays.asList(2, 4), Arrays.asList(3, 5)); + + assertEquals(Arrays.asList(2, 3, 4, 5), list); + } + + @Test + public void sameSizeReverse() throws Exception { + MergerBiFunction<Integer> merger = new MergerBiFunction<Integer>(new Comparator<Integer>() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + List<Integer> list = merger.apply(Arrays.asList(3, 5), Arrays.asList(2, 4)); + + assertEquals(Arrays.asList(2, 3, 4, 5), list); + } +} diff --git a/src/test/java/io/reactivex/internal/util/MiscUtilTest.java b/src/test/java/io/reactivex/internal/util/MiscUtilTest.java index e5129fe47b..464262bce9 100644 --- a/src/test/java/io/reactivex/internal/util/MiscUtilTest.java +++ b/src/test/java/io/reactivex/internal/util/MiscUtilTest.java @@ -75,6 +75,27 @@ public void appendOnlyLinkedArrayListForEachWhile() throws Exception { final List<Integer> out = new ArrayList<Integer>(); + list.forEachWhile(new NonThrowingPredicate<Integer>() { + @Override + public boolean test(Integer t2) { + out.add(t2); + return t2 == 2; + } + }); + + assertEquals(Arrays.asList(1, 2), out); + } + + @Test + public void appendOnlyLinkedArrayListForEachWhileBi() throws Exception { + AppendOnlyLinkedArrayList<Integer> list = new AppendOnlyLinkedArrayList<Integer>(2); + + list.add(1); + list.add(2); + list.add(3); + + final List<Integer> out = new ArrayList<Integer>(); + list.forEachWhile(2, new BiPredicate<Integer, Integer>() { @Override public boolean test(Integer t1, Integer t2) throws Exception { @@ -86,7 +107,6 @@ public boolean test(Integer t1, Integer t2) throws Exception { assertEquals(Arrays.asList(1, 2), out); } - @Test public void appendOnlyLinkedArrayListForEachWhilePreGrow() throws Exception { AppendOnlyLinkedArrayList<Integer> list = new AppendOnlyLinkedArrayList<Integer>(12); diff --git a/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java b/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java index 36bfe52566..f6e463c7da 100644 --- a/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java +++ b/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java @@ -45,6 +45,6 @@ public void errorNotificationCompare() { assertEquals(ex.hashCode(), n1.hashCode()); - assertFalse(n1.equals(NotificationLite.complete())); + assertNotEquals(n1, NotificationLite.complete()); } } diff --git a/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java index e680304893..6be3075191 100644 --- a/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java +++ b/src/test/java/io/reactivex/internal/util/QueueDrainHelperTest.java @@ -16,15 +16,20 @@ import static org.junit.Assert.*; import java.io.IOException; -import java.util.ArrayDeque; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import org.junit.Test; -import org.reactivestreams.Subscription; +import org.reactivestreams.*; +import io.reactivex.Observer; import io.reactivex.TestHelper; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.queue.SpscArrayQueue; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.subscribers.TestSubscriber; public class QueueDrainHelperTest { @@ -123,7 +128,7 @@ public boolean getAsBoolean() throws Exception { @Test public void completeRequestRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final ArrayDeque<Integer> queue = new ArrayDeque<Integer>(); final AtomicLong state = new AtomicLong(); @@ -205,4 +210,673 @@ public boolean getAsBoolean() throws Exception { ts.assertValue(1).assertNoErrors().assertNotComplete(); } + + @Test + public void drainMaxLoopMissingBackpressure() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + q.offer(1); + + QueueDrainHelper.drainMaxLoop(q, ts, false, null, qd); + + ts.assertFailure(MissingBackpressureException.class); + } + + @Test + public void drainMaxLoopMissingBackpressureWithResource() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + q.offer(1); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.drainMaxLoop(q, ts, false, d, qd); + + ts.assertFailure(MissingBackpressureException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void drainMaxLoopDontAccept() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 1; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + q.offer(1); + + QueueDrainHelper.drainMaxLoop(q, ts, false, null, qd); + + ts.assertEmpty(); + } + + @Test + public void checkTerminatedDelayErrorEmpty() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, qd); + + ts.assertResult(); + } + + @Test + public void checkTerminatedDelayErrorNonEmpty() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, false, ts, true, q, qd); + + ts.assertEmpty(); + } + + @Test + public void checkTerminatedDelayErrorEmptyError() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, true, ts, true, q, qd); + + ts.assertFailure(TestException.class); + } + + @Test + public void checkTerminatedNonDelayErrorError() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(new BooleanSubscription()); + + QueueDrain<Integer, Integer> qd = new QueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public long requested() { + return 0; + } + + @Override + public long produced(long n) { + return 0; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public boolean accept(Subscriber<? super Integer> a, Integer v) { + return false; + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, false, ts, false, q, qd); + + ts.assertFailure(TestException.class); + } + + @Test + public void observerCheckTerminatedDelayErrorEmpty() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, true, to, true, q, null, qd); + + to.assertResult(); + } + + @Test + public void observerCheckTerminatedDelayErrorEmptyResource() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.checkTerminated(true, true, to, true, q, d, qd); + + to.assertResult(); + + assertTrue(d.isDisposed()); + } + + @Test + public void observerCheckTerminatedDelayErrorNonEmpty() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return null; + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, false, to, true, q, null, qd); + + to.assertEmpty(); + } + + @Test + public void observerCheckTerminatedDelayErrorEmptyError() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, true, to, true, q, null, qd); + + to.assertFailure(TestException.class); + } + + @Test + public void observerCheckTerminatedNonDelayErrorError() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + QueueDrainHelper.checkTerminated(true, false, to, false, q, null, qd); + + to.assertFailure(TestException.class); + } + + @Test + public void observerCheckTerminatedNonDelayErrorErrorResource() { + TestObserver<Integer> to = new TestObserver<Integer>(); + to.onSubscribe(Disposables.empty()); + + ObservableQueueDrain<Integer, Integer> qd = new ObservableQueueDrain<Integer, Integer>() { + @Override + public boolean cancelled() { + return false; + } + + @Override + public boolean done() { + return false; + } + + @Override + public Throwable error() { + return new TestException(); + } + + @Override + public boolean enter() { + return true; + } + + @Override + public int leave(int m) { + return 0; + } + + @Override + public void accept(Observer<? super Integer> a, Integer v) { + } + }; + + SpscArrayQueue<Integer> q = new SpscArrayQueue<Integer>(32); + + Disposable d = Disposables.empty(); + + QueueDrainHelper.checkTerminated(true, false, to, false, q, d, qd); + + to.assertFailure(TestException.class); + + assertTrue(d.isDisposed()); + } + + @Test + public void postCompleteAlreadyComplete() { + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + Queue<Integer> q = new ArrayDeque<Integer>(); + q.offer(1); + + AtomicLong state = new AtomicLong(QueueDrainHelper.COMPLETED_MASK); + + QueueDrainHelper.postComplete(ts, q, state, new BooleanSupplier() { + @Override + public boolean getAsBoolean() throws Exception { + return false; + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java b/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java index 6086fff7e3..00d3119b32 100644 --- a/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java +++ b/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java @@ -82,22 +82,22 @@ public void normal() { VolatileSizeArrayList<Integer> list2 = new VolatileSizeArrayList<Integer>(); list2.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); - assertFalse(list2.equals(list)); - assertFalse(list.equals(list2)); + assertNotEquals(list2, list); + assertNotEquals(list, list2); list2.add(7); - assertTrue(list2.equals(list)); - assertTrue(list.equals(list2)); + assertEquals(list2, list); + assertEquals(list, list2); List<Integer> list3 = new ArrayList<Integer>(); list3.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); - assertFalse(list3.equals(list)); - assertFalse(list.equals(list3)); + assertNotEquals(list3, list); + assertNotEquals(list, list3); list3.add(7); - assertTrue(list3.equals(list)); - assertTrue(list.equals(list3)); + assertEquals(list3, list); + assertEquals(list, list3); assertEquals(list.hashCode(), list3.hashCode()); assertEquals(list.toString(), list3.toString()); diff --git a/src/test/java/io/reactivex/maybe/MaybeCreateTest.java b/src/test/java/io/reactivex/maybe/MaybeCreateTest.java index e13b5f09cb..90689133d5 100644 --- a/src/test/java/io/reactivex/maybe/MaybeCreateTest.java +++ b/src/test/java/io/reactivex/maybe/MaybeCreateTest.java @@ -15,12 +15,15 @@ import static org.junit.Assert.assertTrue; +import java.util.List; + import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Cancellable; +import io.reactivex.plugins.RxJavaPlugins; public class MaybeCreateTest { @Test(expected = NullPointerException.class) @@ -30,85 +33,111 @@ public void nullArgument() { @Test public void basic() { - final Disposable d = Disposables.empty(); - - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); - - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertResult(1); - - assertTrue(d.isDisposed()); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertResult(1); + + assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithCancellable() { - final Disposable d1 = Disposables.empty(); - final Disposable d2 = Disposables.empty(); - - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d1); - e.setCancellable(new Cancellable() { - @Override - public void cancel() throws Exception { - d2.dispose(); - } - }); - - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertResult(1); - - assertTrue(d1.isDisposed()); - assertTrue(d2.isDisposed()); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d1 = Disposables.empty(); + final Disposable d2 = Disposables.empty(); + + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d1); + e.setCancellable(new Cancellable() { + @Override + public void cancel() throws Exception { + d2.dispose(); + } + }); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertResult(1); + + assertTrue(d1.isDisposed()); + assertTrue(d2.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); - - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); - - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertFailure(TestException.class); - - assertTrue(d.isDisposed()); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertFailure(TestException.class); + + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } - @Test public void basicWithCompletion() { - final Disposable d = Disposables.empty(); - - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); - - e.onComplete(); - e.onSuccess(2); - e.onError(new TestException()); - } - }).test().assertComplete(); - - assertTrue(d.isDisposed()); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); + + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onComplete(); + e.onSuccess(2); + e.onError(new TestException()); + } + }).test().assertComplete(); + + assertTrue(d.isDisposed()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/io/reactivex/maybe/MaybeRetryTest.java b/src/test/java/io/reactivex/maybe/MaybeRetryTest.java new file mode 100644 index 0000000000..0749da742d --- /dev/null +++ b/src/test/java/io/reactivex/maybe/MaybeRetryTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2017-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.maybe; + +import io.reactivex.Maybe; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.functions.Functions; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MaybeRetryTest { + @Test + public void retryTimesPredicateWithMatchingPredicate() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Maybe.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + throw new IllegalArgumentException(); + } + }) + .retry(Integer.MAX_VALUE, new Predicate<Throwable>() { + @Override public boolean test(final Throwable throwable) throws Exception { + return !(throwable instanceof IllegalArgumentException); + } + }) + .test() + .assertFailure(IllegalArgumentException.class); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Maybe.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(2, Functions.alwaysTrue()) + .test() + .assertResult(true); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithNotMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Maybe.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(1, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(2, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithZeroRetries() { + final AtomicInteger atomicInteger = new AtomicInteger(2); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Maybe.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(0, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(1, numberOfSubscribeCalls.get()); + } +} diff --git a/src/test/java/io/reactivex/maybe/MaybeTest.java b/src/test/java/io/reactivex/maybe/MaybeTest.java index e715b37c1e..45b31b0a8a 100644 --- a/src/test/java/io/reactivex/maybe/MaybeTest.java +++ b/src/test/java/io/reactivex/maybe/MaybeTest.java @@ -30,7 +30,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.internal.operators.flowable.FlowableZipTest.ArgsToString; import io.reactivex.internal.operators.maybe.*; import io.reactivex.observers.TestObserver; @@ -89,11 +89,11 @@ public void fromFlowableMany() { public void fromFlowableDisposeComposesThrough() { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Integer> ts = pp.singleElement().test(); + TestObserver<Integer> to = pp.singleElement().test(); assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); } @@ -144,24 +144,24 @@ public void fromObservableMany() { @Test public void fromObservableDisposeComposesThrough() { - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.singleElement().test(false); + TestObserver<Integer> to = ps.singleElement().test(false); - assertTrue(pp.hasObservers()); + assertTrue(ps.hasObservers()); - ts.cancel(); + to.cancel(); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); } @Test public void fromObservableDisposeComposesThroughImmediatelyCancelled() { - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - pp.singleElement().test(true); + ps.singleElement().test(true); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); } @Test @@ -350,7 +350,6 @@ public void completableMaybeCompletable() { Completable.complete().toMaybe().ignoreElement().test().assertResult(); } - @Test public void unsafeCreate() { Maybe.unsafeCreate(new MaybeSource<Integer>() { @@ -381,11 +380,28 @@ public Flowable<Integer> apply(Maybe<Integer> v) throws Exception { .assertResult(1); } + @Test + public void as() { + Maybe.just(1).as(new MaybeConverter<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Maybe<Integer> v) { + return v.toFlowable(); + } + }) + .test() + .assertResult(1); + } + @Test(expected = NullPointerException.class) public void toNull() { Maybe.just(1).to(null); } + @Test(expected = NullPointerException.class) + public void asNull() { + Maybe.just(1).as(null); + } + @Test public void compose() { Maybe.just(1).compose(new MaybeTransformer<Integer, Integer>() { @@ -525,9 +541,9 @@ public boolean test(Integer v) throws Exception { @Test public void cast() { - TestObserver<Number> ts = Maybe.just(1).cast(Number.class).test(); + TestObserver<Number> to = Maybe.just(1).cast(Number.class).test(); // don'n inline this due to the generic type - ts.assertResult((Number)1); + to.assertResult((Number)1); } @Test(expected = NullPointerException.class) @@ -629,7 +645,6 @@ public void accept(Throwable e) throws Exception { assertNotEquals(main, name[0]); } - @Test public void observeOnCompleteThread() { String main = Thread.currentThread().getName(); @@ -671,7 +686,6 @@ public void subscribeOnComplete() { ; } - @Test public void fromAction() { final int[] call = { 0 }; @@ -784,7 +798,6 @@ public void accept(Integer v) throws Exception { .assertFailure(TestException.class); } - @Test public void doOnSubscribe() { final Disposable[] value = { null }; @@ -813,7 +826,6 @@ public void accept(Disposable v) throws Exception { .assertFailure(TestException.class); } - @Test public void doOnCompleteThrows() { Maybe.empty().doOnComplete(new Action() { @@ -845,7 +857,6 @@ public void run() throws Exception { assertEquals(1, call[0]); } - @Test public void doOnDisposeThrows() { List<Throwable> list = TestHelper.trackPluginErrors(); @@ -853,7 +864,7 @@ public void doOnDisposeThrows() { try { PublishProcessor<Integer> pp = PublishProcessor.create(); - TestObserver<Integer> ts = pp.singleElement().doOnDispose(new Action() { + TestObserver<Integer> to = pp.singleElement().doOnDispose(new Action() { @Override public void run() throws Exception { throw new TestException(); @@ -863,11 +874,11 @@ public void run() throws Exception { assertTrue(pp.hasSubscribers()); - ts.cancel(); + to.cancel(); assertFalse(pp.hasSubscribers()); - ts.assertSubscribed() + to.assertSubscribed() .assertNoValues() .assertNoErrors() .assertNotComplete(); @@ -954,7 +965,6 @@ public void run() throws Exception { assertEquals(-1, call[0]); } - @Test public void doAfterTerminateComplete() { final int[] call = { 0 }; @@ -985,7 +995,7 @@ public void sourceThrowsNPE() { try { Maybe.unsafeCreate(new MaybeSource<Object>() { @Override - public void subscribe(MaybeObserver<? super Object> s) { + public void subscribe(MaybeObserver<? super Object> observer) { throw new NullPointerException("Forced failure"); } }).test(); @@ -996,13 +1006,12 @@ public void subscribe(MaybeObserver<? super Object> s) { } } - @Test public void sourceThrowsIAE() { try { Maybe.unsafeCreate(new MaybeSource<Object>() { @Override - public void subscribe(MaybeObserver<? super Object> s) { + public void subscribe(MaybeObserver<? super Object> observer) { throw new IllegalArgumentException("Forced failure"); } }).test(); @@ -1163,6 +1172,7 @@ public void ignoreElementComplete() { .test() .assertResult(); } + @Test public void ignoreElementSuccessMaybe() { Maybe.just(1) @@ -1486,68 +1496,91 @@ public void nullArgument() { @Test public void basic() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onSuccess(1); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertResult(1); - e.onSuccess(1); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertResult(1); + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithError() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); - e.onError(new TestException()); - e.onSuccess(2); - e.onError(new TestException()); - e.onComplete(); - } - }) - .test() - .assertFailure(TestException.class); + e.onError(new TestException()); + e.onSuccess(2); + e.onError(new TestException()); + e.onComplete(); + } + }) + .test() + .assertFailure(TestException.class); + + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test public void basicWithComplete() { - final Disposable d = Disposables.empty(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final Disposable d = Disposables.empty(); - Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { - @Override - public void subscribe(MaybeEmitter<Integer> e) throws Exception { - e.setDisposable(d); + Maybe.<Integer>create(new MaybeOnSubscribe<Integer>() { + @Override + public void subscribe(MaybeEmitter<Integer> e) throws Exception { + e.setDisposable(d); + + e.onComplete(); + e.onSuccess(1); + e.onError(new TestException()); + e.onComplete(); + e.onSuccess(2); + e.onError(new TestException()); + } + }) + .test() + .assertResult(); - e.onComplete(); - e.onSuccess(1); - e.onError(new TestException()); - e.onComplete(); - e.onSuccess(2); - e.onError(new TestException()); - } - }) - .test() - .assertResult(); + assertTrue(d.isDisposed()); - assertTrue(d.isDisposed()); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test(expected = IllegalArgumentException.class) @@ -1606,10 +1639,10 @@ public void ambArray1SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1620,7 +1653,7 @@ public void ambArray1SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @SuppressWarnings("unchecked") @@ -1629,10 +1662,10 @@ public void ambArray2SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1643,7 +1676,7 @@ public void ambArray2SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @SuppressWarnings("unchecked") @@ -1652,10 +1685,10 @@ public void ambArray1SignalsError() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1665,7 +1698,7 @@ public void ambArray1SignalsError() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @SuppressWarnings("unchecked") @@ -1674,10 +1707,10 @@ public void ambArray2SignalsError() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1687,7 +1720,7 @@ public void ambArray2SignalsError() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @SuppressWarnings("unchecked") @@ -1696,10 +1729,10 @@ public void ambArray1SignalsComplete() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1709,7 +1742,7 @@ public void ambArray1SignalsComplete() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } @SuppressWarnings("unchecked") @@ -1718,10 +1751,10 @@ public void ambArray2SignalsComplete() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) + TestObserver<Integer> to = Maybe.ambArray(pp1.singleElement(), pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1731,7 +1764,7 @@ public void ambArray2SignalsComplete() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } @SuppressWarnings("unchecked") @@ -1740,10 +1773,10 @@ public void ambIterable1SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1754,7 +1787,7 @@ public void ambIterable1SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @SuppressWarnings("unchecked") @@ -1763,10 +1796,10 @@ public void ambIterable2SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1777,7 +1810,7 @@ public void ambIterable2SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @SuppressWarnings("unchecked") @@ -1786,10 +1819,10 @@ public void ambIterable2SignalsSuccessWithOverlap() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1801,7 +1834,7 @@ public void ambIterable2SignalsSuccessWithOverlap() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @SuppressWarnings("unchecked") @@ -1810,10 +1843,10 @@ public void ambIterable1SignalsError() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1823,7 +1856,7 @@ public void ambIterable1SignalsError() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @SuppressWarnings("unchecked") @@ -1832,10 +1865,10 @@ public void ambIterable2SignalsError() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1845,7 +1878,7 @@ public void ambIterable2SignalsError() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } @SuppressWarnings("unchecked") @@ -1854,10 +1887,10 @@ public void ambIterable2SignalsErrorWithOverlap() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1868,7 +1901,7 @@ public void ambIterable2SignalsErrorWithOverlap() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertFailureAndMessage(TestException.class, "2"); + to.assertFailureAndMessage(TestException.class, "2"); } @SuppressWarnings("unchecked") @@ -1877,10 +1910,10 @@ public void ambIterable1SignalsComplete() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1890,7 +1923,7 @@ public void ambIterable1SignalsComplete() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } @SuppressWarnings("unchecked") @@ -1899,10 +1932,10 @@ public void ambIterable2SignalsComplete() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) + TestObserver<Integer> to = Maybe.amb(Arrays.asList(pp1.singleElement(), pp2.singleElement())) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -1912,7 +1945,7 @@ public void ambIterable2SignalsComplete() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(); + to.assertResult(); } @Test(expected = NullPointerException.class) @@ -2059,30 +2092,30 @@ public void mergeArrayBackpressuredMixed3() { @SuppressWarnings("unchecked") @Test public void mergeArrayFused() { - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.mergeArray(Maybe.just(1), Maybe.just(2), Maybe.just(3)).subscribe(ts); ts.assertSubscribed() .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1, 2, 3); } @SuppressWarnings("unchecked") @Test public void mergeArrayFusedRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp1 = PublishProcessor.create(); final PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.mergeArray(pp1.singleElement(), pp2.singleElement()).subscribe(ts); ts.assertSubscribed() .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) ; TestHelper.race(new Runnable() { @@ -2097,7 +2130,7 @@ public void run() { pp2.onNext(1); pp2.onComplete(); } - }, Schedulers.single()); + }); ts .awaitDone(5, TimeUnit.SECONDS) @@ -2193,14 +2226,14 @@ public void mergeALotFused() { Maybe<Integer>[] sources = new Maybe[Flowable.bufferSize() * 2]; Arrays.fill(sources, Maybe.just(1)); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); Maybe.mergeArray(sources).subscribe(ts); ts .assertSubscribed() .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertValueCount(sources.length) .assertNoErrors() .assertComplete(); @@ -2349,21 +2382,28 @@ public void accept(Integer v, Throwable e) throws Exception { @Test public void doOnEventError() { - final List<Object> list = new ArrayList<Object>(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final List<Object> list = new ArrayList<Object>(); - TestException ex = new TestException(); + TestException ex = new TestException(); - assertTrue(Maybe.<Integer>error(ex) - .doOnEvent(new BiConsumer<Integer, Throwable>() { - @Override - public void accept(Integer v, Throwable e) throws Exception { - list.add(v); - list.add(e); - } - }) - .subscribe().isDisposed()); + assertTrue(Maybe.<Integer>error(ex) + .doOnEvent(new BiConsumer<Integer, Throwable>() { + @Override + public void accept(Integer v, Throwable e) throws Exception { + list.add(v); + list.add(e); + } + }) + .subscribe().isDisposed()); + + assertEquals(Arrays.asList(null, ex), list); - assertEquals(Arrays.asList(null, ex), list); + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } } @Test @@ -2403,7 +2443,7 @@ public void accept(Integer v, Throwable e) throws Exception { @Test public void doOnEventErrorThrows() { - TestObserver<Integer> ts = Maybe.<Integer>error(new TestException("Outer")) + TestObserver<Integer> to = Maybe.<Integer>error(new TestException("Outer")) .doOnEvent(new BiConsumer<Integer, Throwable>() { @Override public void accept(Integer v, Throwable e) throws Exception { @@ -2413,13 +2453,12 @@ public void accept(Integer v, Throwable e) throws Exception { .test() .assertFailure(CompositeException.class); - List<Throwable> list = TestHelper.compositeList(ts.errors().get(0)); + List<Throwable> list = TestHelper.compositeList(to.errors().get(0)); TestHelper.assertError(list, 0, TestException.class, "Outer"); TestHelper.assertError(list, 1, TestException.class, "Inner"); assertEquals(2, list.size()); } - @Test public void doOnEventCompleteThrows() { Maybe.<Integer>empty() @@ -2863,7 +2902,6 @@ public void zipArray() { .assertResult("[1]"); } - @SuppressWarnings("unchecked") @Test public void zipIterable() { @@ -2966,16 +3004,15 @@ public void zip9() { .assertResult("123456789"); } - @Test public void ambWith1SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = pp1.singleElement().ambWith(pp2.singleElement()) + TestObserver<Integer> to = pp1.singleElement().ambWith(pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -2986,7 +3023,7 @@ public void ambWith1SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -2994,10 +3031,10 @@ public void ambWith2SignalsSuccess() { PublishProcessor<Integer> pp1 = PublishProcessor.create(); PublishProcessor<Integer> pp2 = PublishProcessor.create(); - TestObserver<Integer> ts = pp1.singleElement().ambWith(pp2.singleElement()) + TestObserver<Integer> to = pp1.singleElement().ambWith(pp2.singleElement()) .test(); - ts.assertEmpty(); + to.assertEmpty(); assertTrue(pp1.hasSubscribers()); assertTrue(pp2.hasSubscribers()); @@ -3008,7 +3045,7 @@ public void ambWith2SignalsSuccess() { assertFalse(pp1.hasSubscribers()); assertFalse(pp2.hasSubscribers()); - ts.assertResult(2); + to.assertResult(2); } @Test @@ -3148,6 +3185,19 @@ public Publisher<Object> apply(Flowable<? extends Throwable> v) throws Exception return (Publisher)v; } }).test().assertResult(1); + + final AtomicInteger calls = new AtomicInteger(); + try { + Maybe.error(new Callable<Throwable>() { + @Override + public Throwable call() { + calls.incrementAndGet(); + return new TestException(); + } + }).retry(5).test(); + } finally { + assertEquals(6, calls.get()); + } } @Test diff --git a/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java b/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java index 4d4d3753c4..5b2206001d 100644 --- a/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java +++ b/src/test/java/io/reactivex/observable/ObservableCovarianceTest.java @@ -47,7 +47,7 @@ public void testCovarianceOfFrom() { @Test public void testSortedList() { - Comparator<Media> SORT_FUNCTION = new Comparator<Media>() { + Comparator<Media> sortFunction = new Comparator<Media>() { @Override public int compare(Media t1, Media t2) { return 1; @@ -56,17 +56,17 @@ public int compare(Media t1, Media t2) { // this one would work without the covariance generics Observable<Media> o = Observable.just(new Movie(), new TVSeason(), new Album()); - o.toSortedList(SORT_FUNCTION); + o.toSortedList(sortFunction); // this one would NOT work without the covariance generics Observable<Movie> o2 = Observable.just(new Movie(), new ActionMovie(), new HorrorMovie()); - o2.toSortedList(SORT_FUNCTION); + o2.toSortedList(sortFunction); } @Test public void testGroupByCompose() { Observable<Movie> movies = Observable.just(new HorrorMovie(), new ActionMovie(), new Movie()); - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); movies .groupBy(new Function<Movie, Object>() { @Override @@ -105,11 +105,11 @@ public String apply(Movie v) { }); } }) - .subscribe(ts); - ts.assertTerminated(); - ts.assertNoErrors(); + .subscribe(to); + to.assertTerminated(); + to.assertNoErrors(); // System.out.println(ts.getOnNextEvents()); - assertEquals(6, ts.valueCount()); + assertEquals(6, to.valueCount()); } @SuppressWarnings("unused") diff --git a/src/test/java/io/reactivex/observable/ObservableFuseableTest.java b/src/test/java/io/reactivex/observable/ObservableFuseableTest.java index ba65019910..070810461c 100644 --- a/src/test/java/io/reactivex/observable/ObservableFuseableTest.java +++ b/src/test/java/io/reactivex/observable/ObservableFuseableTest.java @@ -17,7 +17,7 @@ import org.junit.Test; import io.reactivex.Observable; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.ObserverFusion; public class ObservableFuseableTest { @@ -26,8 +26,8 @@ public class ObservableFuseableTest { public void syncRange() { Observable.range(1, 10) - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -37,8 +37,8 @@ public void syncRange() { public void syncArray() { Observable.fromArray(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -48,8 +48,8 @@ public void syncArray() { public void syncIterable() { Observable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.SYNC)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -59,9 +59,9 @@ public void syncIterable() { public void syncRangeHidden() { Observable.range(1, 10).hide() - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) .assertOf(ObserverFusion.<Integer>assertNotFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -71,9 +71,9 @@ public void syncRangeHidden() { public void syncArrayHidden() { Observable.fromArray(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) .hide() - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) .assertOf(ObserverFusion.<Integer>assertNotFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); @@ -83,9 +83,9 @@ public void syncArrayHidden() { public void syncIterableHidden() { Observable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) .hide() - .to(ObserverFusion.<Integer>test(QueueSubscription.ANY, false)) + .to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)) .assertOf(ObserverFusion.<Integer>assertNotFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.NONE)) .assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertNoErrors() .assertComplete(); diff --git a/src/test/java/io/reactivex/observable/ObservableMergeTests.java b/src/test/java/io/reactivex/observable/ObservableMergeTests.java index c7ee01c591..1d68a5d28e 100644 --- a/src/test/java/io/reactivex/observable/ObservableMergeTests.java +++ b/src/test/java/io/reactivex/observable/ObservableMergeTests.java @@ -68,7 +68,7 @@ public void testMergeCovariance3() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } @@ -91,7 +91,7 @@ public Observable<Movie> call() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } diff --git a/src/test/java/io/reactivex/observable/ObservableNullTests.java b/src/test/java/io/reactivex/observable/ObservableNullTests.java index 7f58c9a057..d4142b34af 100644 --- a/src/test/java/io/reactivex/observable/ObservableNullTests.java +++ b/src/test/java/io/reactivex/observable/ObservableNullTests.java @@ -368,11 +368,11 @@ public void fromFutureReturnsNull() { FutureTask<Object> f = new FutureTask<Object>(Functions.EMPTY_RUNNABLE, null); f.run(); - TestObserver<Object> ts = new TestObserver<Object>(); - Observable.fromFuture(f).subscribe(ts); - ts.assertNoValues(); - ts.assertNotComplete(); - ts.assertError(NullPointerException.class); + TestObserver<Object> to = new TestObserver<Object>(); + Observable.fromFuture(f).subscribe(to); + to.assertNoValues(); + to.assertNotComplete(); + to.assertError(NullPointerException.class); } @Test(expected = NullPointerException.class) @@ -546,7 +546,7 @@ public void intervalPeriodSchedulerNull() { @Test(expected = NullPointerException.class) public void intervalRangeUnitNull() { - Observable.intervalRange(1,1, 1, 1, null); + Observable.intervalRange(1, 1, 1, 1, null); } @Test(expected = NullPointerException.class) @@ -1088,7 +1088,7 @@ public Iterator<Object> iterator() { @Test(expected = NullPointerException.class) public void concatWithNull() { - just1.concatWith(null); + just1.concatWith((ObservableSource<Integer>)null); } @Test(expected = NullPointerException.class) @@ -1301,7 +1301,7 @@ public void doOnLifecycleOnSubscribeNull() { public void doOnLifecycleOnDisposeNull() { just1.doOnLifecycle(new Consumer<Disposable>() { @Override - public void accept(Disposable s) { } + public void accept(Disposable d) { } }, null); } @@ -1662,7 +1662,7 @@ public void liftNull() { public void liftReturnsNull() { just1.lift(new ObservableOperator<Object, Integer>() { @Override - public Observer<? super Integer> apply(Observer<? super Object> s) { + public Observer<? super Integer> apply(Observer<? super Object> observer) { return null; } }).blockingSubscribe(); @@ -1685,7 +1685,7 @@ public Object apply(Integer v) { @Test(expected = NullPointerException.class) public void mergeWithNull() { - just1.mergeWith(null); + just1.mergeWith((ObservableSource<Integer>)null); } @Test(expected = NullPointerException.class) @@ -2408,6 +2408,11 @@ public void toNull() { just1.to(null); } + @Test(expected = NullPointerException.class) + public void asNull() { + just1.as(null); + } + @Test(expected = NullPointerException.class) public void toListNull() { just1.toList(null); @@ -2763,7 +2768,6 @@ public Object apply(Integer a, Integer b) { }); } - @Test(expected = NullPointerException.class) public void zipWithCombinerNull() { just1.zipWith(just1, null); diff --git a/src/test/java/io/reactivex/observable/ObservableReduceTests.java b/src/test/java/io/reactivex/observable/ObservableReduceTests.java index 5a08fd2d65..a5bdacf48b 100644 --- a/src/test/java/io/reactivex/observable/ObservableReduceTests.java +++ b/src/test/java/io/reactivex/observable/ObservableReduceTests.java @@ -77,7 +77,6 @@ public Movie apply(Movie t1, Movie t2) { assertNotNull(reduceResult2); } - @Test public void reduceInts() { Observable<Integer> o = Observable.just(1, 2, 3); diff --git a/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java b/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java index a6761dc9e4..848d6128e1 100644 --- a/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java +++ b/src/test/java/io/reactivex/observable/ObservableSubscriberTest.java @@ -174,13 +174,12 @@ public void methodTestCancelled() { @Test public void safeSubscriberAlreadySafe() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).safeSubscribe(new SafeObserver<Integer>(ts)); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).safeSubscribe(new SafeObserver<Integer>(to)); - ts.assertResult(1); + to.assertResult(1); } - @Test public void methodTestNoCancel() { PublishSubject<Integer> ps = PublishSubject.create(); @@ -206,7 +205,7 @@ public Observer apply(Observable a, Observer b) throws Exception { Observable.just(1).test(); fail("Should have thrown"); } catch (NullPointerException ex) { - assertEquals("Plugin returned null Observer", ex.getMessage()); + assertEquals("The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins", ex.getMessage()); } } finally { RxJavaPlugins.reset(); @@ -215,7 +214,7 @@ public Observer apply(Observable a, Observer b) throws Exception { static final class BadObservable extends Observable<Integer> { @Override - protected void subscribeActual(Observer<? super Integer> s) { + protected void subscribeActual(Observer<? super Integer> observer) { throw new IllegalArgumentException(); } } diff --git a/src/test/java/io/reactivex/observable/ObservableTest.java b/src/test/java/io/reactivex/observable/ObservableTest.java index 24653d1ff3..f59a41857e 100644 --- a/src/test/java/io/reactivex/observable/ObservableTest.java +++ b/src/test/java/io/reactivex/observable/ObservableTest.java @@ -28,6 +28,7 @@ import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.observables.ConnectableObservable; import io.reactivex.observers.*; import io.reactivex.schedulers.*; @@ -145,7 +146,6 @@ public Throwable call() { verify(w, times(1)).onError(any(RuntimeException.class)); } - @Test public void testCountAFewItems() { Observable<String> o = Observable.just("a", "b", "c", "d"); @@ -346,7 +346,7 @@ public void testOnSubscribeFails() { final RuntimeException re = new RuntimeException("bad impl"); Observable<String> o = Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { throw re; } + public void subscribe(Observer<? super String> observer) { throw re; } }); o.subscribe(observer); @@ -358,7 +358,8 @@ public void testOnSubscribeFails() { @Test public void testMaterializeDematerializeChaining() { Observable<Integer> obs = Observable.just(1); - Observable<Integer> chained = obs.materialize().dematerialize(); + Observable<Integer> chained = obs.materialize() + .dematerialize(Functions.<Notification<Integer>>identity()); Observer<Integer> observer = TestHelper.mockObserver(); @@ -566,7 +567,7 @@ public void run() { }).replay(); // we connect immediately and it will emit the value - Disposable s = o.connect(); + Disposable connection = o.connect(); try { // we then expect the following 2 subscriptions to get that same value @@ -595,7 +596,7 @@ public void accept(String v) { } assertEquals(1, counter.get()); } finally { - s.dispose(); + connection.dispose(); } } @@ -1026,30 +1027,30 @@ public void testRangeWithScheduler() { @Test public void testMergeWith() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).mergeWith(Observable.just(2)).subscribe(ts); - ts.assertValues(1, 2); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).mergeWith(Observable.just(2)).subscribe(to); + to.assertValues(1, 2); } @Test public void testConcatWith() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).concatWith(Observable.just(2)).subscribe(ts); - ts.assertValues(1, 2); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).concatWith(Observable.just(2)).subscribe(to); + to.assertValues(1, 2); } @Test public void testAmbWith() { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).ambWith(Observable.just(2)).subscribe(ts); - ts.assertValue(1); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).ambWith(Observable.just(2)).subscribe(to); + to.assertValue(1); } @Test public void testTakeWhileToList() { final int expectedCount = 3; final AtomicInteger count = new AtomicInteger(); - for (int i = 0;i < expectedCount; i++) { + for (int i = 0; i < expectedCount; i++) { Observable .just(Boolean.TRUE, Boolean.FALSE) .takeWhile(new Predicate<Boolean>() { @@ -1072,7 +1073,7 @@ public void accept(List<Boolean> booleans) { @Test public void testCompose() { - TestObserver<String> ts = new TestObserver<String>(); + TestObserver<String> to = new TestObserver<String>(); Observable.just(1, 2, 3).compose(new ObservableTransformer<Integer, String>() { @Override public Observable<String> apply(Observable<Integer> t1) { @@ -1084,10 +1085,10 @@ public String apply(Integer v) { }); } }) - .subscribe(ts); - ts.assertTerminated(); - ts.assertNoErrors(); - ts.assertValues("1", "2", "3"); + .subscribe(to); + to.assertTerminated(); + to.assertNoErrors(); + to.assertValues("1", "2", "3"); } @Test @@ -1097,7 +1098,7 @@ public void testErrorThrownIssue1685() { Observable.error(new RuntimeException("oops")) .materialize() .delay(1, TimeUnit.SECONDS) - .dematerialize() + .dematerialize(Functions.<Notification<Object>>identity()) .subscribe(subject); subject.subscribe(); @@ -1151,18 +1152,48 @@ public void testForEachWithNull() { @Test public void testExtend() { - final TestObserver<Object> subscriber = new TestObserver<Object>(); + final TestObserver<Object> to = new TestObserver<Object>(); final Object value = new Object(); - Observable.just(value).to(new Function<Observable<Object>, Object>() { + Object returned = Observable.just(value).to(new Function<Observable<Object>, Object>() { @Override public Object apply(Observable<Object> onSubscribe) { - onSubscribe.subscribe(subscriber); - subscriber.assertNoErrors(); - subscriber.assertComplete(); - subscriber.assertValue(value); - return subscriber.values().get(0); + onSubscribe.subscribe(to); + to.assertNoErrors(); + to.assertComplete(); + to.assertValue(value); + return to.values().get(0); } }); + assertSame(returned, value); + } + + @Test + public void testAsExtend() { + final TestObserver<Object> to = new TestObserver<Object>(); + final Object value = new Object(); + Object returned = Observable.just(value).as(new ObservableConverter<Object, Object>() { + @Override + public Object apply(Observable<Object> onSubscribe) { + onSubscribe.subscribe(to); + to.assertNoErrors(); + to.assertComplete(); + to.assertValue(value); + return to.values().get(0); + } + }); + assertSame(returned, value); + } + + @Test + public void as() { + Observable.just(1).as(new ObservableConverter<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Observable<Integer> v) { + return v.toFlowable(BackpressureStrategy.MISSING); + } + }) + .test() + .assertResult(1); } @Test diff --git a/src/test/java/io/reactivex/observable/ObservableWindowTests.java b/src/test/java/io/reactivex/observable/ObservableWindowTests.java index d4c68fd63b..701b07e0b7 100644 --- a/src/test/java/io/reactivex/observable/ObservableWindowTests.java +++ b/src/test/java/io/reactivex/observable/ObservableWindowTests.java @@ -16,11 +16,16 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.Test; import io.reactivex.Observable; +import io.reactivex.SingleSource; import io.reactivex.functions.*; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subjects.PublishSubject; public class ObservableWindowTests { @@ -50,4 +55,43 @@ public void accept(List<Integer> xs) { assertEquals(2, lists.size()); } + + @Test + public void timeSizeWindowAlternatingBounds() { + TestScheduler scheduler = new TestScheduler(); + PublishSubject<Integer> ps = PublishSubject.create(); + + TestObserver<List<Integer>> to = ps.window(5, TimeUnit.SECONDS, scheduler, 2) + .flatMapSingle(new Function<Observable<Integer>, SingleSource<List<Integer>>>() { + @Override + public SingleSource<List<Integer>> apply(Observable<Integer> v) { + return v.toList(); + } + }) + .test(); + + ps.onNext(1); + ps.onNext(2); + to.assertValueCount(1); // size bound hit + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + ps.onNext(3); + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + to.assertValueCount(2); // time bound hit + + ps.onNext(4); + ps.onNext(5); + + to.assertValueCount(3); // size bound hit again + + ps.onNext(4); + + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + + to.assertValueCount(4) + .assertNoErrors() + .assertNotComplete(); + + to.dispose(); + } } diff --git a/src/test/java/io/reactivex/observers/ObserverFusion.java b/src/test/java/io/reactivex/observers/ObserverFusion.java index 5e81b02216..05b694eba6 100644 --- a/src/test/java/io/reactivex/observers/ObserverFusion.java +++ b/src/test/java/io/reactivex/observers/ObserverFusion.java @@ -32,7 +32,7 @@ public enum ObserverFusion { * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) + * .to(ObserverFusion.test(QueueFuseable.ANY, false)) * .assertResult(0); * </pre> * @param <T> the value type @@ -52,7 +52,7 @@ public static <T> Function<Observable<T>, TestObserver<T>> test( * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) + * .to(ObserverFusion.test(0, QueueFuseable.ANY, false)) * .assertOf(ObserverFusion.assertFuseable()); * </pre> * @param <T> the value type @@ -71,8 +71,8 @@ static final class AssertFusionConsumer<T> implements Consumer<TestObserver<T>> } @Override - public void accept(TestObserver<T> ts) throws Exception { - ts.assertFusionMode(mode); + public void accept(TestObserver<T> to) throws Exception { + to.assertFusionMode(mode); } } @@ -87,21 +87,21 @@ static final class TestFunctionCallback<T> implements Function<Observable<T>, Te @Override public TestObserver<T> apply(Observable<T> t) throws Exception { - TestObserver<T> ts = new TestObserver<T>(); - ts.setInitialFusionMode(mode); + TestObserver<T> to = new TestObserver<T>(); + to.setInitialFusionMode(mode); if (cancelled) { - ts.cancel(); + to.cancel(); } - t.subscribe(ts); - return ts; + t.subscribe(to); + return to; } } enum AssertFuseable implements Consumer<TestObserver<Object>> { INSTANCE; @Override - public void accept(TestObserver<Object> ts) throws Exception { - ts.assertFuseable(); + public void accept(TestObserver<Object> to) throws Exception { + to.assertFuseable(); } } @@ -112,7 +112,7 @@ public void accept(TestObserver<Object> ts) throws Exception { * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) + * .to(ObserverFusion.test(0, QueueFuseable.ANY, false)) * .assertOf(ObserverFusion.assertNotFuseable()); * </pre> * @param <T> the value type @@ -126,8 +126,8 @@ public static <T> Consumer<TestObserver<T>> assertNotFuseable() { enum AssertNotFuseable implements Consumer<TestObserver<Object>> { INSTANCE; @Override - public void accept(TestObserver<Object> ts) throws Exception { - ts.assertNotFuseable(); + public void accept(TestObserver<Object> to) throws Exception { + to.assertNotFuseable(); } } @@ -139,40 +139,39 @@ public void accept(TestObserver<Object> ts) throws Exception { * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) - * .assertOf(ObserverFusion.assertFusionMode(QueueDisposable.SYNC)); + * .to(ObserverFusion.test(0, QueueFuseable.ANY, false)) + * .assertOf(ObserverFusion.assertFusionMode(QueueFuseable.SYNC)); * </pre> * @param <T> the value type - * @param mode the expected established fusion mode, see {@link QueueDisposable} constants. + * @param mode the expected established fusion mode, see {@link QueueFuseable} constants. * @return the new Consumer instance */ public static <T> Consumer<TestObserver<T>> assertFusionMode(final int mode) { return new AssertFusionConsumer<T>(mode); } - /** * Constructs a TestObserver with the given required fusion mode. * @param <T> the value type - * @param mode the requested fusion mode, see {@link QueueSubscription} constants + * @param mode the requested fusion mode, see {@link QueueFuseable} constants * @return the new TestSubscriber */ public static <T> TestObserver<T> newTest(int mode) { - TestObserver<T> ts = new TestObserver<T>(); - ts.setInitialFusionMode(mode); - return ts; + TestObserver<T> to = new TestObserver<T>(); + to.setInitialFusionMode(mode); + return to; } /** - * Assert that the TestSubscriber received a fuseabe QueueSubscription and + * Assert that the TestSubscriber received a fuseabe QueueFuseable.and * is in the given fusion mode. * @param <T> the value type - * @param ts the TestSubscriber instance + * @param to the TestSubscriber instance * @param mode the expected mode * @return the TestSubscriber */ - public static <T> TestObserver<T> assertFusion(TestObserver<T> ts, int mode) { - return ts.assertOf(ObserverFusion.<T>assertFuseable()) + public static <T> TestObserver<T> assertFusion(TestObserver<T> to, int mode) { + return to.assertOf(ObserverFusion.<T>assertFuseable()) .assertOf(ObserverFusion.<T>assertFusionMode(mode)); } } diff --git a/src/test/java/io/reactivex/observers/SafeObserverTest.java b/src/test/java/io/reactivex/observers/SafeObserverTest.java index cf92a7139c..edd725b7ed 100644 --- a/src/test/java/io/reactivex/observers/SafeObserverTest.java +++ b/src/test/java/io/reactivex/observers/SafeObserverTest.java @@ -447,15 +447,17 @@ static class SafeObserverTestException extends RuntimeException { @Ignore("Observers can't throw") public void testOnCompletedThrows() { final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); - SafeObserver<Integer> s = new SafeObserver<Integer>(new DefaultObserver<Integer>() { + SafeObserver<Integer> observer = new SafeObserver<Integer>(new DefaultObserver<Integer>() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable e) { error.set(e); } + @Override public void onComplete() { throw new TestException(); @@ -463,7 +465,7 @@ public void onComplete() { }); try { - s.onComplete(); + observer.onComplete(); Assert.fail(); } catch (RuntimeException e) { assertNull(error.get()); @@ -476,29 +478,31 @@ public void testActual() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable e) { } + @Override public void onComplete() { } }; - SafeObserver<Integer> s = new SafeObserver<Integer>(actual); + SafeObserver<Integer> observer = new SafeObserver<Integer>(actual); - assertSame(actual, s.actual); + assertSame(actual, observer.downstream); } @Test public void dispose() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); Disposable d = Disposables.empty(); so.onSubscribe(d); - ts.dispose(); + to.dispose(); assertTrue(d.isDisposed()); @@ -507,9 +511,9 @@ public void dispose() { @Test public void onNextAfterComplete() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -523,14 +527,14 @@ public void onNextAfterComplete() { so.onComplete(); - ts.assertResult(); + to.assertResult(); } @Test public void onNextNull() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -538,50 +542,50 @@ public void onNextNull() { so.onNext(null); - ts.assertFailure(NullPointerException.class); + to.assertFailure(NullPointerException.class); } @Test public void onNextWithoutOnSubscribe() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); so.onNext(1); - ts.assertFailureAndMessage(NullPointerException.class, "Subscription not set!"); + to.assertFailureAndMessage(NullPointerException.class, "Subscription not set!"); } @Test public void onErrorWithoutOnSubscribe() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); so.onError(new TestException()); - ts.assertFailure(CompositeException.class); + to.assertFailure(CompositeException.class); - TestHelper.assertError(ts, 0, TestException.class); - TestHelper.assertError(ts, 1, NullPointerException.class, "Subscription not set!"); + TestHelper.assertError(to, 0, TestException.class); + TestHelper.assertError(to, 1, NullPointerException.class, "Subscription not set!"); } @Test public void onCompleteWithoutOnSubscribe() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); so.onComplete(); - ts.assertFailureAndMessage(NullPointerException.class, "Subscription not set!"); + to.assertFailureAndMessage(NullPointerException.class, "Subscription not set!"); } @Test public void onNextNormal() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SafeObserver<Integer> so = new SafeObserver<Integer>(ts); + SafeObserver<Integer> so = new SafeObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -590,7 +594,7 @@ public void onNextNormal() { so.onNext(1); so.onComplete(); - ts.assertResult(1); + to.assertResult(1); } static final class CrashDummy implements Observer<Object>, Disposable { diff --git a/src/test/java/io/reactivex/observers/SerializedObserverTest.java b/src/test/java/io/reactivex/observers/SerializedObserverTest.java index f330ce0b37..f094ad7fc7 100644 --- a/src/test/java/io/reactivex/observers/SerializedObserverTest.java +++ b/src/test/java/io/reactivex/observers/SerializedObserverTest.java @@ -156,6 +156,7 @@ public void testMultiThreadedWithNPEinMiddle() { @Test public void runOutOfOrderConcurrencyTest() { ExecutorService tp = Executors.newFixedThreadPool(20); + List<Throwable> errors = TestHelper.trackPluginErrors(); try { TestConcurrencySubscriber tw = new TestConcurrencySubscriber(); // we need Synchronized + SafeObserver to handle synchronization plus life-cycle @@ -190,6 +191,10 @@ public void runOutOfOrderConcurrencyTest() { @SuppressWarnings("unused") int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior // System.out.println("Number of events executed: " + numNextEvents); + + for (int i = 0; i < errors.size(); i++) { + TestHelper.assertUndeliverable(errors, i, RuntimeException.class); + } } catch (Throwable e) { fail("Concurrency test failed: " + e.getMessage()); e.printStackTrace(); @@ -200,6 +205,8 @@ public void runOutOfOrderConcurrencyTest() { } catch (InterruptedException e) { e.printStackTrace(); } + + RxJavaPlugins.reset(); } } @@ -435,11 +442,11 @@ private static Observable<String> infinite(final AtomicInteger produced) { return Observable.unsafeCreate(new ObservableSource<String>() { @Override - public void subscribe(Observer<? super String> s) { + public void subscribe(Observer<? super String> observer) { Disposable bs = Disposables.empty(); - s.onSubscribe(bs); + observer.onSubscribe(bs); while (!bs.isDisposed()) { - s.onNext("onNext"); + observer.onNext("onNext"); produced.incrementAndGet(); } } @@ -955,30 +962,38 @@ public void onNext(Integer t) { @Test public void testErrorReentry() { - final AtomicReference<Observer<Integer>> serial = new AtomicReference<Observer<Integer>>(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Observer<Integer>> serial = new AtomicReference<Observer<Integer>>(); - TestObserver<Integer> ts = new TestObserver<Integer>() { - @Override - public void onNext(Integer v) { - serial.get().onError(new TestException()); - serial.get().onError(new TestException()); - super.onNext(v); - } - }; - SerializedObserver<Integer> sobs = new SerializedObserver<Integer>(ts); - sobs.onSubscribe(Disposables.empty()); - serial.set(sobs); + TestObserver<Integer> to = new TestObserver<Integer>() { + @Override + public void onNext(Integer v) { + serial.get().onError(new TestException()); + serial.get().onError(new TestException()); + super.onNext(v); + } + }; + SerializedObserver<Integer> sobs = new SerializedObserver<Integer>(to); + sobs.onSubscribe(Disposables.empty()); + serial.set(sobs); - sobs.onNext(1); + sobs.onNext(1); + + to.assertValue(1); + to.assertError(TestException.class); - ts.assertValue(1); - ts.assertError(TestException.class); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } + @Test public void testCompleteReentry() { final AtomicReference<Observer<Integer>> serial = new AtomicReference<Observer<Integer>>(); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer v) { serial.get().onComplete(); @@ -986,22 +1001,22 @@ public void onNext(Integer v) { super.onNext(v); } }; - SerializedObserver<Integer> sobs = new SerializedObserver<Integer>(ts); + SerializedObserver<Integer> sobs = new SerializedObserver<Integer>(to); sobs.onSubscribe(Disposables.empty()); serial.set(sobs); sobs.onNext(1); - ts.assertValue(1); - ts.assertComplete(); - ts.assertNoErrors(); + to.assertValue(1); + to.assertComplete(); + to.assertNoErrors(); } @Test public void dispose() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -1009,7 +1024,7 @@ public void dispose() { assertFalse(so.isDisposed()); - ts.cancel(); + to.cancel(); assertTrue(so.isDisposed()); @@ -1018,10 +1033,10 @@ public void dispose() { @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -1034,9 +1049,9 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); - ts.awaitDone(5, TimeUnit.SECONDS) + to.awaitDone(5, TimeUnit.SECONDS) .assertResult(); } @@ -1044,10 +1059,10 @@ public void run() { @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -1067,23 +1082,23 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS) + to.awaitDone(5, TimeUnit.SECONDS) .assertNoErrors() .assertComplete(); - assertTrue(ts.valueCount() <= 1); + assertTrue(to.valueCount() <= 1); } } @Test public void onNextOnErrorRace() { - for (int i = 0; i < 500; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -1105,23 +1120,23 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS) + to.awaitDone(5, TimeUnit.SECONDS) .assertError(ex) .assertNotComplete(); - assertTrue(ts.valueCount() <= 1); + assertTrue(to.valueCount() <= 1); } } @Test public void onNextOnErrorRaceDelayError() { - for (int i = 0; i < 500; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts, true); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to, true); Disposable d = Disposables.empty(); @@ -1143,13 +1158,13 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS) + to.awaitDone(5, TimeUnit.SECONDS) .assertError(ex) .assertNotComplete(); - assertTrue(ts.valueCount() <= 1); + assertTrue(to.valueCount() <= 1); } } @@ -1160,9 +1175,9 @@ public void startOnce() { List<Throwable> error = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); so.onSubscribe(Disposables.empty()); @@ -1180,13 +1195,13 @@ public void startOnce() { @Test public void onCompleteOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { List<Throwable> errors = TestHelper.trackPluginErrors(); try { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - final SerializedObserver<Integer> so = new SerializedObserver<Integer>(ts); + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); Disposable d = Disposables.empty(); @@ -1208,14 +1223,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS); + to.awaitDone(5, TimeUnit.SECONDS); - if (ts.completions() != 0) { - ts.assertResult(); + if (to.completions() != 0) { + to.assertResult(); } else { - ts.assertFailure(TestException.class).assertError(ex); + to.assertFailure(TestException.class).assertError(ex); } for (Throwable e : errors) { @@ -1227,4 +1242,20 @@ public void run() { } } + + @Test + public void nullOnNext() { + + TestObserver<Integer> to = new TestObserver<Integer>(); + + final SerializedObserver<Integer> so = new SerializedObserver<Integer>(to); + + Disposable d = Disposables.empty(); + + so.onSubscribe(d); + + so.onNext(null); + + to.assertFailureAndMessage(NullPointerException.class, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + } } diff --git a/src/test/java/io/reactivex/observers/TestObserverTest.java b/src/test/java/io/reactivex/observers/TestObserverTest.java index 63b9c2b584..d2d81dc77c 100644 --- a/src/test/java/io/reactivex/observers/TestObserverTest.java +++ b/src/test/java/io/reactivex/observers/TestObserverTest.java @@ -32,7 +32,7 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.observable.ObservableScalarXMap.ScalarDisposable; import io.reactivex.internal.subscriptions.EmptySubscription; import io.reactivex.processors.PublishProcessor; @@ -48,68 +48,68 @@ public class TestObserverTest { @Test public void testAssert() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + oi.subscribe(subscriber); - o.assertValues(1, 2); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertValues(1, 2); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test public void testAssertNotMatchCount() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + oi.subscribe(subscriber); thrown.expect(AssertionError.class); // FIXME different message format // thrown.expectMessage("Number of items does not match. Provided: 1 Actual: 2"); - o.assertValue(1); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertValue(1); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test public void testAssertNotMatchValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + oi.subscribe(subscriber); thrown.expect(AssertionError.class); // FIXME different message format // thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); - o.assertValues(1, 3); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertValues(1, 3); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test public void assertNeverAtNotMatchingValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + oi.subscribe(subscriber); - o.assertNever(3); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertNever(3); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test public void assertNeverAtMatchingValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + oi.subscribe(subscriber); - o.assertValues(1, 2); + subscriber.assertValues(1, 2); thrown.expect(AssertionError.class); - o.assertNever(2); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertNever(2); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test @@ -147,8 +147,8 @@ public boolean test(final Integer o) throws Exception { @Test public void testAssertTerminalEventNotReceived() { PublishProcessor<Integer> p = PublishProcessor.create(); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - p.subscribe(o); + TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); + p.subscribe(subscriber); p.onNext(1); p.onNext(2); @@ -157,36 +157,36 @@ public void testAssertTerminalEventNotReceived() { // FIXME different message format // thrown.expectMessage("No terminal events received."); - o.assertValues(1, 2); - o.assertValueCount(2); - o.assertTerminated(); + subscriber.assertValues(1, 2); + subscriber.assertValueCount(2); + subscriber.assertTerminated(); } @Test public void testWrappingMock() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - Subscriber<Integer> mockObserver = TestHelper.mockSubscriber(); + Subscriber<Integer> mockSubscriber = TestHelper.mockSubscriber(); - oi.subscribe(new TestSubscriber<Integer>(mockObserver)); + oi.subscribe(new TestSubscriber<Integer>(mockSubscriber)); - InOrder inOrder = inOrder(mockObserver); - inOrder.verify(mockObserver, times(1)).onNext(1); - inOrder.verify(mockObserver, times(1)).onNext(2); - inOrder.verify(mockObserver, times(1)).onComplete(); + InOrder inOrder = inOrder(mockSubscriber); + inOrder.verify(mockSubscriber, times(1)).onNext(1); + inOrder.verify(mockSubscriber, times(1)).onNext(2); + inOrder.verify(mockSubscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testWrappingMockWhenUnsubscribeInvolved() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)).take(2); - Subscriber<Integer> mockObserver = TestHelper.mockSubscriber(); - oi.subscribe(new TestSubscriber<Integer>(mockObserver)); + Subscriber<Integer> mockSubscriber = TestHelper.mockSubscriber(); + oi.subscribe(new TestSubscriber<Integer>(mockSubscriber)); - InOrder inOrder = inOrder(mockObserver); - inOrder.verify(mockObserver, times(1)).onNext(1); - inOrder.verify(mockObserver, times(1)).onNext(2); - inOrder.verify(mockObserver, times(1)).onComplete(); + InOrder inOrder = inOrder(mockSubscriber); + inOrder.verify(mockSubscriber, times(1)).onNext(1); + inOrder.verify(mockSubscriber, times(1)).onNext(2); + inOrder.verify(mockSubscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -197,46 +197,46 @@ public void testErrorSwallowed() { @Test public void testGetEvents() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onSubscribe(EmptySubscription.INSTANCE); - to.onNext(1); - to.onNext(2); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onSubscribe(EmptySubscription.INSTANCE); + ts.onNext(1); + ts.onNext(2); assertEquals(Arrays.<Object>asList(Arrays.asList(1, 2), Collections.emptyList(), - Collections.emptyList()), to.getEvents()); + Collections.emptyList()), ts.getEvents()); - to.onComplete(); + ts.onComplete(); assertEquals(Arrays.<Object>asList(Arrays.asList(1, 2), Collections.emptyList(), - Collections.singletonList(Notification.createOnComplete())), to.getEvents()); + Collections.singletonList(Notification.createOnComplete())), ts.getEvents()); TestException ex = new TestException(); - TestSubscriber<Integer> to2 = new TestSubscriber<Integer>(); - to2.onSubscribe(EmptySubscription.INSTANCE); - to2.onNext(1); - to2.onNext(2); + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + ts2.onSubscribe(EmptySubscription.INSTANCE); + ts2.onNext(1); + ts2.onNext(2); assertEquals(Arrays.<Object>asList(Arrays.asList(1, 2), Collections.emptyList(), - Collections.emptyList()), to2.getEvents()); + Collections.emptyList()), ts2.getEvents()); - to2.onError(ex); + ts2.onError(ex); assertEquals(Arrays.<Object>asList( Arrays.asList(1, 2), Collections.singletonList(ex), Collections.emptyList()), - to2.getEvents()); + ts2.getEvents()); } @Test public void testNullExpected() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onNext(1); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onNext(1); try { - to.assertValue((Integer) null); + ts.assertValue((Integer) null); } catch (AssertionError ex) { // this is expected return; @@ -246,11 +246,11 @@ public void testNullExpected() { @Test public void testNullActual() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onNext(null); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onNext(null); try { - to.assertValue(1); + ts.assertValue(1); } catch (AssertionError ex) { // this is expected return; @@ -260,26 +260,27 @@ public void testNullActual() { @Test public void testTerminalErrorOnce() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onError(new TestException()); - to.onError(new TestException()); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onError(new TestException()); + ts.onError(new TestException()); try { - to.assertTerminated(); + ts.assertTerminated(); } catch (AssertionError ex) { // this is expected return; } fail("Failed to report multiple onError terminal events!"); } + @Test public void testTerminalCompletedOnce() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onComplete(); - to.onComplete(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onComplete(); + ts.onComplete(); try { - to.assertTerminated(); + ts.assertTerminated(); } catch (AssertionError ex) { // this is expected return; @@ -289,12 +290,12 @@ public void testTerminalCompletedOnce() { @Test public void testTerminalOneKind() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onError(new TestException()); - to.onComplete(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + ts.onError(new TestException()); + ts.onComplete(); try { - to.assertTerminated(); + ts.assertTerminated(); } catch (AssertionError ex) { // this is expected return; @@ -304,68 +305,68 @@ public void testTerminalOneKind() { @Test public void createDelegate() { - TestObserver<Integer> ts1 = TestObserver.create(); + TestObserver<Integer> to1 = TestObserver.create(); - TestObserver<Integer> ts = TestObserver.create(ts1); + TestObserver<Integer> to = TestObserver.create(to1); - ts.assertNotSubscribed(); + to.assertNotSubscribed(); - assertFalse(ts.hasSubscription()); + assertFalse(to.hasSubscription()); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); try { - ts.assertNotSubscribed(); + to.assertNotSubscribed(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } - assertTrue(ts.hasSubscription()); + assertTrue(to.hasSubscription()); - assertFalse(ts.isDisposed()); + assertFalse(to.isDisposed()); - ts.onNext(1); - ts.onError(new TestException()); - ts.onComplete(); + to.onNext(1); + to.onError(new TestException()); + to.onComplete(); - ts1.assertValue(1).assertError(TestException.class).assertComplete(); + to1.assertValue(1).assertError(TestException.class).assertComplete(); - ts.dispose(); + to.dispose(); - assertTrue(ts.isDisposed()); + assertTrue(to.isDisposed()); - assertTrue(ts.isTerminated()); + assertTrue(to.isTerminated()); - assertSame(Thread.currentThread(), ts.lastThread()); + assertSame(Thread.currentThread(), to.lastThread()); try { - ts.assertNoValues(); + to.assertNoValues(); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertValueCount(0); + to.assertValueCount(0); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } - ts.assertValueSequence(Collections.singletonList(1)); + to.assertValueSequence(Collections.singletonList(1)); try { - ts.assertValueSequence(Collections.singletonList(2)); + to.assertValueSequence(Collections.singletonList(2)); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } - ts.assertValueSet(Collections.singleton(1)); + to.assertValueSet(Collections.singleton(1)); try { - ts.assertValueSet(Collections.singleton(2)); + to.assertValueSet(Collections.singleton(2)); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected @@ -375,117 +376,115 @@ public void createDelegate() { @Test public void assertError() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); try { - ts.assertError(TestException.class); + to.assertError(TestException.class); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertError(new TestException()); + to.assertError(new TestException()); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertError(Functions.<Throwable>alwaysTrue()); + to.assertError(Functions.<Throwable>alwaysTrue()); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertErrorMessage(""); + to.assertErrorMessage(""); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertSubscribed(); + to.assertSubscribed(); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertTerminated(); + to.assertTerminated(); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.assertSubscribed(); + to.assertSubscribed(); - ts.assertNoErrors(); + to.assertNoErrors(); TestException ex = new TestException("Forced failure"); - ts.onError(ex); + to.onError(ex); - ts.assertError(ex); + to.assertError(ex); - ts.assertError(TestException.class); + to.assertError(TestException.class); - ts.assertError(Functions.<Throwable>alwaysTrue()); + to.assertError(Functions.<Throwable>alwaysTrue()); - ts.assertError(new Predicate<Throwable>() { + to.assertError(new Predicate<Throwable>() { @Override public boolean test(Throwable t) throws Exception { return t.getMessage() != null && t.getMessage().contains("Forced"); } }); - ts.assertErrorMessage("Forced failure"); + to.assertErrorMessage("Forced failure"); try { - ts.assertErrorMessage(""); + to.assertErrorMessage(""); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertError(new RuntimeException()); + to.assertError(new RuntimeException()); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertError(IOException.class); + to.assertError(IOException.class); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertError(Functions.<Throwable>alwaysFalse()); + to.assertError(Functions.<Throwable>alwaysFalse()); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } try { - ts.assertNoErrors(); + to.assertNoErrors(); throw new RuntimeException("Should have thrown"); } catch (AssertionError exc) { // expected } - ts.assertTerminated(); - - ts.assertValueCount(0); - - ts.assertNoValues(); + to.assertTerminated(); + to.assertValueCount(0); + to.assertNoValues(); } @Test @@ -502,67 +501,67 @@ public void valueAndClass() { @Test public void assertFailure() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.onError(new TestException("Forced failure")); + to.onError(new TestException("Forced failure")); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); - ts.assertFailure(Functions.<Throwable>alwaysTrue()); + to.assertFailure(Functions.<Throwable>alwaysTrue()); - ts.assertFailureAndMessage(TestException.class, "Forced failure"); + to.assertFailureAndMessage(TestException.class, "Forced failure"); - ts.onNext(1); + to.onNext(1); - ts.assertFailure(TestException.class, 1); + to.assertFailure(TestException.class, 1); - ts.assertFailure(Functions.<Throwable>alwaysTrue(), 1); + to.assertFailure(Functions.<Throwable>alwaysTrue(), 1); - ts.assertFailureAndMessage(TestException.class, "Forced failure", 1); + to.assertFailureAndMessage(TestException.class, "Forced failure", 1); } @Test public void assertFuseable() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.assertNotFuseable(); + to.assertNotFuseable(); try { - ts.assertFuseable(); + to.assertFuseable(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertFusionMode(QueueDisposable.SYNC); + to.assertFusionMode(QueueFuseable.SYNC); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } - ts = TestObserver.create(); - ts.setInitialFusionMode(QueueDisposable.ANY); + to = TestObserver.create(); + to.setInitialFusionMode(QueueFuseable.ANY); - ts.onSubscribe(new ScalarDisposable<Integer>(ts, 1)); + to.onSubscribe(new ScalarDisposable<Integer>(to, 1)); - ts.assertFuseable(); + to.assertFuseable(); - ts.assertFusionMode(QueueDisposable.SYNC); + to.assertFusionMode(QueueFuseable.SYNC); try { - ts.assertFusionMode(QueueDisposable.NONE); + to.assertFusionMode(QueueFuseable.NONE); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertNotFuseable(); + to.assertNotFuseable(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected @@ -572,14 +571,14 @@ public void assertFuseable() { @Test public void assertTerminated() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.assertNotTerminated(); + to.assertNotTerminated(); - ts.onError(null); + to.onError(null); try { - ts.assertNotTerminated(); + to.assertNotTerminated(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected @@ -588,9 +587,9 @@ public void assertTerminated() { @Test public void assertOf() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.assertOf(new Consumer<TestObserver<Integer>>() { + to.assertOf(new Consumer<TestObserver<Integer>>() { @Override public void accept(TestObserver<Integer> f) throws Exception { f.assertNotSubscribed(); @@ -598,7 +597,7 @@ public void accept(TestObserver<Integer> f) throws Exception { }); try { - ts.assertOf(new Consumer<TestObserver<Integer>>() { + to.assertOf(new Consumer<TestObserver<Integer>>() { @Override public void accept(TestObserver<Integer> f) throws Exception { f.assertSubscribed(); @@ -610,7 +609,7 @@ public void accept(TestObserver<Integer> f) throws Exception { } try { - ts.assertOf(new Consumer<TestObserver<Integer>>() { + to.assertOf(new Consumer<TestObserver<Integer>>() { @Override public void accept(TestObserver<Integer> f) throws Exception { throw new IllegalArgumentException(); @@ -624,34 +623,34 @@ public void accept(TestObserver<Integer> f) throws Exception { @Test public void assertResult() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.onComplete(); + to.onComplete(); - ts.assertResult(); + to.assertResult(); try { - ts.assertResult(1); + to.assertResult(1); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } - ts.onNext(1); + to.onNext(1); - ts.assertResult(1); + to.assertResult(1); try { - ts.assertResult(2); + to.assertResult(2); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertResult(); + to.assertResult(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected @@ -661,139 +660,139 @@ public void assertResult() { @Test(timeout = 5000) public void await() throws Exception { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - assertFalse(ts.await(100, TimeUnit.MILLISECONDS)); + assertFalse(to.await(100, TimeUnit.MILLISECONDS)); - ts.awaitDone(100, TimeUnit.MILLISECONDS); + to.awaitDone(100, TimeUnit.MILLISECONDS); - assertTrue(ts.isDisposed()); + assertTrue(to.isDisposed()); - assertFalse(ts.awaitTerminalEvent(100, TimeUnit.MILLISECONDS)); + assertFalse(to.awaitTerminalEvent(100, TimeUnit.MILLISECONDS)); - assertEquals(0, ts.completions()); - assertEquals(0, ts.errorCount()); + assertEquals(0, to.completions()); + assertEquals(0, to.errorCount()); - ts.onComplete(); + to.onComplete(); - assertTrue(ts.await(100, TimeUnit.MILLISECONDS)); + assertTrue(to.await(100, TimeUnit.MILLISECONDS)); - ts.await(); + to.await(); - ts.awaitDone(5, TimeUnit.SECONDS); + to.awaitDone(5, TimeUnit.SECONDS); - assertEquals(1, ts.completions()); - assertEquals(0, ts.errorCount()); + assertEquals(1, to.completions()); + assertEquals(0, to.errorCount()); - assertTrue(ts.awaitTerminalEvent()); + assertTrue(to.awaitTerminalEvent()); - final TestObserver<Integer> ts1 = TestObserver.create(); + final TestObserver<Integer> to1 = TestObserver.create(); - ts1.onSubscribe(Disposables.empty()); + to1.onSubscribe(Disposables.empty()); Schedulers.single().scheduleDirect(new Runnable() { @Override public void run() { - ts1.onComplete(); + to1.onComplete(); } }, 200, TimeUnit.MILLISECONDS); - ts1.await(); + to1.await(); - ts1.assertValueSet(Collections.<Integer>emptySet()); + to1.assertValueSet(Collections.<Integer>emptySet()); } @Test public void errors() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - assertEquals(0, ts.errors().size()); + assertEquals(0, to.errors().size()); - ts.onError(new TestException()); + to.onError(new TestException()); - assertEquals(1, ts.errors().size()); + assertEquals(1, to.errors().size()); - TestHelper.assertError(ts.errors(), 0, TestException.class); + TestHelper.assertError(to.errors(), 0, TestException.class); } @SuppressWarnings("unchecked") @Test public void onNext() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - assertEquals(0, ts.valueCount()); + assertEquals(0, to.valueCount()); - assertEquals(Collections.emptyList(), ts.values()); + assertEquals(Collections.emptyList(), to.values()); - ts.onNext(1); + to.onNext(1); - assertEquals(Collections.singletonList(1), ts.values()); + assertEquals(Collections.singletonList(1), to.values()); - ts.cancel(); + to.cancel(); - assertTrue(ts.isCancelled()); - assertTrue(ts.isDisposed()); + assertTrue(to.isCancelled()); + assertTrue(to.isDisposed()); - ts.assertValue(1); + to.assertValue(1); - assertEquals(Arrays.asList(Collections.singletonList(1), Collections.emptyList(), Collections.emptyList()), ts.getEvents()); + assertEquals(Arrays.asList(Collections.singletonList(1), Collections.emptyList(), Collections.emptyList()), to.getEvents()); - ts.onComplete(); + to.onComplete(); - assertEquals(Arrays.asList(Collections.singletonList(1), Collections.emptyList(), Collections.singletonList(Notification.createOnComplete())), ts.getEvents()); + assertEquals(Arrays.asList(Collections.singletonList(1), Collections.emptyList(), Collections.singletonList(Notification.createOnComplete())), to.getEvents()); } @Test public void fusionModeToString() { - assertEquals("NONE", TestObserver.fusionModeToString(QueueDisposable.NONE)); - assertEquals("SYNC", TestObserver.fusionModeToString(QueueDisposable.SYNC)); - assertEquals("ASYNC", TestObserver.fusionModeToString(QueueDisposable.ASYNC)); + assertEquals("NONE", TestObserver.fusionModeToString(QueueFuseable.NONE)); + assertEquals("SYNC", TestObserver.fusionModeToString(QueueFuseable.SYNC)); + assertEquals("ASYNC", TestObserver.fusionModeToString(QueueFuseable.ASYNC)); assertEquals("Unknown(100)", TestObserver.fusionModeToString(100)); } @Test public void multipleTerminals() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.assertNotComplete(); + to.assertNotComplete(); - ts.onComplete(); + to.onComplete(); try { - ts.assertNotComplete(); + to.assertNotComplete(); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected } - ts.assertTerminated(); + to.assertTerminated(); - ts.onComplete(); + to.onComplete(); try { - ts.assertComplete(); + to.assertComplete(); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected } try { - ts.assertTerminated(); + to.assertTerminated(); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected } try { - ts.assertNotComplete(); + to.assertNotComplete(); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected @@ -802,32 +801,32 @@ public void multipleTerminals() { @Test public void assertValue() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); try { - ts.assertValue(1); + to.assertValue(1); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected } - ts.onNext(1); + to.onNext(1); - ts.assertValue(1); + to.assertValue(1); try { - ts.assertValue(2); + to.assertValue(2); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected } - ts.onNext(2); + to.onNext(2); try { - ts.assertValue(1); + to.assertValue(1); throw new RuntimeException("Should have thrown"); } catch (Throwable ex) { // expected @@ -836,77 +835,76 @@ public void assertValue() { @Test public void onNextMisbehave() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onNext(1); + to.onNext(1); - ts.assertError(IllegalStateException.class); + to.assertError(IllegalStateException.class); - ts = TestObserver.create(); + to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.onNext(null); + to.onNext(null); - ts.assertFailure(NullPointerException.class, (Integer)null); + to.assertFailure(NullPointerException.class, (Integer)null); } @Test public void awaitTerminalEventInterrupt() { - final TestObserver<Integer> ts = TestObserver.create(); + final TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); Thread.currentThread().interrupt(); - ts.awaitTerminalEvent(); + to.awaitTerminalEvent(); assertTrue(Thread.interrupted()); Thread.currentThread().interrupt(); - ts.awaitTerminalEvent(5, TimeUnit.SECONDS); + to.awaitTerminalEvent(5, TimeUnit.SECONDS); assertTrue(Thread.interrupted()); } @Test public void assertTerminated2() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - assertFalse(ts.isTerminated()); + assertFalse(to.isTerminated()); - ts.onError(new TestException()); - ts.onError(new IOException()); + to.onError(new TestException()); + to.onError(new IOException()); - assertTrue(ts.isTerminated()); + assertTrue(to.isTerminated()); try { - ts.assertTerminated(); + to.assertTerminated(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } try { - ts.assertError(TestException.class); + to.assertError(TestException.class); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } + to = TestObserver.create(); - ts = TestObserver.create(); - - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.onError(new TestException()); - ts.onComplete(); + to.onError(new TestException()); + to.onComplete(); try { - ts.assertTerminated(); + to.assertTerminated(); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected @@ -915,30 +913,30 @@ public void assertTerminated2() { @Test public void onSubscribe() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(null); + to.onSubscribe(null); - ts.assertError(NullPointerException.class); + to.assertError(NullPointerException.class); - ts = TestObserver.create(); + to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); Disposable d1 = Disposables.empty(); - ts.onSubscribe(d1); + to.onSubscribe(d1); assertTrue(d1.isDisposed()); - ts.assertError(IllegalStateException.class); + to.assertError(IllegalStateException.class); - ts = TestObserver.create(); - ts.dispose(); + to = TestObserver.create(); + to.dispose(); d1 = Disposables.empty(); - ts.onSubscribe(d1); + to.onSubscribe(d1); assertTrue(d1.isDisposed()); @@ -946,56 +944,56 @@ public void onSubscribe() { @Test public void assertValueSequence() { - TestObserver<Integer> ts = TestObserver.create(); + TestObserver<Integer> to = TestObserver.create(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.onNext(1); - ts.onNext(2); + to.onNext(1); + to.onNext(2); try { - ts.assertValueSequence(Collections.<Integer>emptyList()); + to.assertValueSequence(Collections.<Integer>emptyList()); throw new RuntimeException("Should have thrown"); - } catch (AssertionError ex) { - // expected + } catch (AssertionError expected) { + assertTrue(expected.getMessage(), expected.getMessage().startsWith("More values received than expected (0)")); } try { - ts.assertValueSequence(Collections.singletonList(1)); + to.assertValueSequence(Collections.singletonList(1)); throw new RuntimeException("Should have thrown"); - } catch (AssertionError ex) { - // expected + } catch (AssertionError expected) { + assertTrue(expected.getMessage(), expected.getMessage().startsWith("More values received than expected (1)")); } - ts.assertValueSequence(Arrays.asList(1, 2)); + to.assertValueSequence(Arrays.asList(1, 2)); try { - ts.assertValueSequence(Arrays.asList(1, 2, 3)); + to.assertValueSequence(Arrays.asList(1, 2, 3)); throw new RuntimeException("Should have thrown"); - } catch (AssertionError ex) { - // expected + } catch (AssertionError expected) { + assertTrue(expected.getMessage(), expected.getMessage().startsWith("Fewer values received than expected (2)")); } } @Test public void assertEmpty() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); try { - ts.assertEmpty(); + to.assertEmpty(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected } - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); - ts.assertEmpty(); + to.assertEmpty(); - ts.onNext(1); + to.onNext(1); try { - ts.assertEmpty(); + to.assertEmpty(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected @@ -1004,12 +1002,12 @@ public void assertEmpty() { @Test public void awaitDoneTimed() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); Thread.currentThread().interrupt(); try { - ts.awaitDone(5, TimeUnit.SECONDS); + to.awaitDone(5, TimeUnit.SECONDS); } catch (RuntimeException ex) { assertTrue(ex.toString(), ex.getCause() instanceof InterruptedException); } @@ -1017,14 +1015,14 @@ public void awaitDoneTimed() { @Test public void assertNotSubscribed() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - ts.assertNotSubscribed(); + to.assertNotSubscribed(); - ts.errors().add(new TestException()); + to.errors().add(new TestException()); try { - ts.assertNotSubscribed(); + to.assertNotSubscribed(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected @@ -1033,32 +1031,32 @@ public void assertNotSubscribed() { @Test public void assertErrorMultiple() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); TestException e = new TestException(); - ts.errors().add(e); - ts.errors().add(new TestException()); + to.errors().add(e); + to.errors().add(new TestException()); try { - ts.assertError(TestException.class); + to.assertError(TestException.class); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected } try { - ts.assertError(e); + to.assertError(e); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected } try { - ts.assertError(Functions.<Throwable>alwaysTrue()); + to.assertError(Functions.<Throwable>alwaysTrue()); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected } try { - ts.assertErrorMessage(""); + to.assertErrorMessage(""); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected @@ -1067,10 +1065,10 @@ public void assertErrorMultiple() { @Test public void testErrorInPredicate() { - TestObserver<Object> ts = new TestObserver<Object>(); - ts.onError(new RuntimeException()); + TestObserver<Object> to = new TestObserver<Object>(); + to.onError(new RuntimeException()); try { - ts.assertError(new Predicate<Throwable>() { + to.assertError(new Predicate<Throwable>() { @Override public boolean test(Throwable throwable) throws Exception { throw new TestException(); @@ -1085,25 +1083,25 @@ public boolean test(Throwable throwable) throws Exception { @Test public void assertComplete() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); try { - ts.assertComplete(); + to.assertComplete(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected } - ts.onComplete(); + to.onComplete(); - ts.assertComplete(); + to.assertComplete(); - ts.onComplete(); + to.onComplete(); try { - ts.assertComplete(); + to.assertComplete(); throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { // expected @@ -1112,16 +1110,16 @@ public void assertComplete() { @Test public void completeWithoutOnSubscribe() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - ts.onComplete(); + to.onComplete(); - ts.assertError(IllegalStateException.class); + to.assertError(IllegalStateException.class); } @Test public void completeDelegateThrows() { - TestObserver<Integer> ts = new TestObserver<Integer>(new Observer<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>(new Observer<Integer>() { @Override public void onSubscribe(Disposable d) { @@ -1145,19 +1143,19 @@ public void onComplete() { }); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); try { - ts.onComplete(); + to.onComplete(); throw new RuntimeException("Should have thrown!"); } catch (TestException ex) { - assertTrue(ts.isTerminated()); + assertTrue(to.isTerminated()); } } @Test public void errorDelegateThrows() { - TestObserver<Integer> ts = new TestObserver<Integer>(new Observer<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>(new Observer<Integer>() { @Override public void onSubscribe(Disposable d) { @@ -1181,38 +1179,38 @@ public void onComplete() { }); - ts.onSubscribe(Disposables.empty()); + to.onSubscribe(Disposables.empty()); try { - ts.onError(new IOException()); + to.onError(new IOException()); throw new RuntimeException("Should have thrown!"); } catch (TestException ex) { - assertTrue(ts.isTerminated()); + assertTrue(to.isTerminated()); } } @Test public void syncQueueThrows() { - TestObserver<Object> ts = new TestObserver<Object>(); - ts.setInitialFusionMode(QueueDisposable.SYNC); + TestObserver<Object> to = new TestObserver<Object>(); + to.setInitialFusionMode(QueueFuseable.SYNC); Observable.range(1, 5) .map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) - .subscribe(ts); + .subscribe(to); - ts.assertSubscribed() + to.assertSubscribed() .assertFuseable() - .assertFusionMode(QueueDisposable.SYNC) + .assertFusionMode(QueueFuseable.SYNC) .assertFailure(TestException.class); } @Test public void asyncQueueThrows() { - TestObserver<Object> ts = new TestObserver<Object>(); - ts.setInitialFusionMode(QueueDisposable.ANY); + TestObserver<Object> to = new TestObserver<Object>(); + to.setInitialFusionMode(QueueFuseable.ANY); UnicastSubject<Integer> up = UnicastSubject.create(); @@ -1221,13 +1219,13 @@ public void asyncQueueThrows() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) - .subscribe(ts); + .subscribe(to); up.onNext(1); - ts.assertSubscribed() + to.assertSubscribed() .assertFuseable() - .assertFusionMode(QueueDisposable.ASYNC) + .assertFusionMode(QueueFuseable.ASYNC) .assertFailure(TestException.class); } @@ -1249,32 +1247,32 @@ public void errorMeansDisposed() { @Test public void asyncFusion() { - TestObserver<Object> ts = new TestObserver<Object>(); - ts.setInitialFusionMode(QueueDisposable.ANY); + TestObserver<Object> to = new TestObserver<Object>(); + to.setInitialFusionMode(QueueFuseable.ANY); UnicastSubject<Integer> up = UnicastSubject.create(); up - .subscribe(ts); + .subscribe(to); up.onNext(1); up.onComplete(); - ts.assertSubscribed() + to.assertSubscribed() .assertFuseable() - .assertFusionMode(QueueDisposable.ASYNC) + .assertFusionMode(QueueFuseable.ASYNC) .assertResult(1); } @Test public void assertValuePredicateEmpty() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.empty().subscribe(ts); + Observable.empty().subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("No values"); - ts.assertValue(new Predicate<Object>() { + to.assertValue(new Predicate<Object>() { @Override public boolean test(final Object o) throws Exception { return false; } @@ -1283,11 +1281,11 @@ public void assertValuePredicateEmpty() { @Test public void assertValuePredicateMatch() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1).subscribe(ts); + Observable.just(1).subscribe(to); - ts.assertValue(new Predicate<Integer>() { + to.assertValue(new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o == 1; } @@ -1296,13 +1294,13 @@ public void assertValuePredicateMatch() { @Test public void assertValuePredicateNoMatch() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1).subscribe(ts); + Observable.just(1).subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("Value not present"); - ts.assertValue(new Predicate<Integer>() { + to.assertValue(new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o != 1; } @@ -1311,13 +1309,13 @@ public void assertValuePredicateNoMatch() { @Test public void assertValuePredicateMatchButMore() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1, 2).subscribe(ts); + Observable.just(1, 2).subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("Value present but other values as well"); - ts.assertValue(new Predicate<Integer>() { + to.assertValue(new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o == 1; } @@ -1326,13 +1324,13 @@ public void assertValuePredicateMatchButMore() { @Test public void assertValueAtPredicateEmpty() { - TestObserver<Object> ts = new TestObserver<Object>(); + TestObserver<Object> to = new TestObserver<Object>(); - Observable.empty().subscribe(ts); + Observable.empty().subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("No values"); - ts.assertValueAt(0, new Predicate<Object>() { + to.assertValueAt(0, new Predicate<Object>() { @Override public boolean test(final Object o) throws Exception { return false; } @@ -1341,11 +1339,11 @@ public void assertValueAtPredicateEmpty() { @Test public void assertValueAtPredicateMatch() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1, 2).subscribe(ts); + Observable.just(1, 2).subscribe(to); - ts.assertValueAt(1, new Predicate<Integer>() { + to.assertValueAt(1, new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o == 2; } @@ -1354,13 +1352,13 @@ public void assertValueAtPredicateMatch() { @Test public void assertValueAtPredicateNoMatch() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1, 2, 3).subscribe(ts); + Observable.just(1, 2, 3).subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("Value not present"); - ts.assertValueAt(2, new Predicate<Integer>() { + to.assertValueAt(2, new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o != 3; } @@ -1369,19 +1367,61 @@ public void assertValueAtPredicateNoMatch() { @Test public void assertValueAtInvalidIndex() { - TestObserver<Integer> ts = new TestObserver<Integer>(); + TestObserver<Integer> to = new TestObserver<Integer>(); - Observable.just(1, 2).subscribe(ts); + Observable.just(1, 2).subscribe(to); thrown.expect(AssertionError.class); thrown.expectMessage("Invalid index: 2 (latch = 0, values = 2, errors = 0, completions = 1)"); - ts.assertValueAt(2, new Predicate<Integer>() { + to.assertValueAt(2, new Predicate<Integer>() { @Override public boolean test(final Integer o) throws Exception { return o == 1; } }); } + @Test + public void assertValueAtIndexEmpty() { + TestObserver<Object> to = new TestObserver<Object>(); + + Observable.empty().subscribe(to); + + thrown.expect(AssertionError.class); + thrown.expectMessage("No values"); + to.assertValueAt(0, "a"); + } + + @Test + public void assertValueAtIndexMatch() { + TestObserver<String> to = new TestObserver<String>(); + + Observable.just("a", "b").subscribe(to); + + to.assertValueAt(1, "b"); + } + + @Test + public void assertValueAtIndexNoMatch() { + TestObserver<String> to = new TestObserver<String>(); + + Observable.just("a", "b", "c").subscribe(to); + + thrown.expect(AssertionError.class); + thrown.expectMessage("expected: b (class: String) but was: c (class: String) (latch = 0, values = 3, errors = 0, completions = 1)"); + to.assertValueAt(2, "b"); + } + + @Test + public void assertValueAtIndexInvalidIndex() { + TestObserver<String> to = new TestObserver<String>(); + + Observable.just("a", "b").subscribe(to); + + thrown.expect(AssertionError.class); + thrown.expectMessage("Invalid index: 2 (latch = 0, values = 2, errors = 0, completions = 1)"); + to.assertValueAt(2, "c"); + } + @Test public void withTag() { try { @@ -1392,9 +1432,229 @@ public void withTag() { .assertResult(1) ; } - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.toString().contains("testing with item=2")); } } + + @Test + public void assertValuesOnly() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValuesOnly(); + + to.onNext(5); + to.assertValuesOnly(5); + + to.onNext(-1); + to.assertValuesOnly(5, -1); + } + + @Test + public void assertValuesOnlyThrowsOnUnexpectedValue() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValuesOnly(); + + to.onNext(5); + to.assertValuesOnly(5); + + to.onNext(-1); + + try { + to.assertValuesOnly(5); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValuesOnlyThrowsWhenCompleted() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onComplete(); + + try { + to.assertValuesOnly(); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValuesOnlyThrowsWhenErrored() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onError(new TestException()); + + try { + to.assertValuesOnly(); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnly() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValueSetOnly(Collections.<Integer>emptySet()); + + to.onNext(5); + to.assertValueSetOnly(Collections.singleton(5)); + + to.onNext(-1); + to.assertValueSetOnly(new HashSet<Integer>(Arrays.asList(5, -1))); + } + + @Test + public void assertValueSetOnlyThrowsOnUnexpectedValue() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValueSetOnly(Collections.<Integer>emptySet()); + + to.onNext(5); + to.assertValueSetOnly(Collections.singleton(5)); + + to.onNext(-1); + + try { + to.assertValueSetOnly(Collections.singleton(5)); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnlyThrowsWhenCompleted() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onComplete(); + + try { + to.assertValueSetOnly(Collections.<Integer>emptySet()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnlyThrowsWhenErrored() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onError(new TestException()); + + try { + to.assertValueSetOnly(Collections.<Integer>emptySet()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnly() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValueSequenceOnly(Collections.<Integer>emptyList()); + + to.onNext(5); + to.assertValueSequenceOnly(Collections.singletonList(5)); + + to.onNext(-1); + to.assertValueSequenceOnly(Arrays.asList(5, -1)); + } + + @Test + public void assertValueSequenceOnlyThrowsOnUnexpectedValue() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + to.assertValueSequenceOnly(Collections.<Integer>emptyList()); + + to.onNext(5); + to.assertValueSequenceOnly(Collections.singletonList(5)); + + to.onNext(-1); + + try { + to.assertValueSequenceOnly(Collections.singletonList(5)); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnlyThrowsWhenCompleted() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onComplete(); + + try { + to.assertValueSequenceOnly(Collections.<Integer>emptyList()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnlyThrowsWhenErrored() { + TestObserver<Integer> to = TestObserver.create(); + to.onSubscribe(Disposables.empty()); + + to.onError(new TestException()); + + try { + to.assertValueSequenceOnly(Collections.<Integer>emptyList()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetWiderSet() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); + + Observable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set); + } + + @Test + public void assertValueSetExact() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(1, 2, 3, 4, 5)); + + Observable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set) + .assertValueCount(set.size()); + } + + @Test + public void assertValueSetMissing() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(0, 1, 2, 4, 5, 6, 7)); + + try { + Observable.range(1, 5) + .test() + .assertValueSet(set); + + throw new RuntimeException("Should have failed"); + } catch (AssertionError ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("Value not in the expected collection: " + 3)); + } + } } diff --git a/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java b/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java index 3a40edc229..41f82363d6 100644 --- a/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelDoOnNextTryTest.java @@ -49,6 +49,7 @@ public void doOnNextNoError() { calls = 0; } } + @Test public void doOnNextErrorNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java b/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java index bb7d919ce0..e49090acf8 100644 --- a/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelFilterTryTest.java @@ -94,6 +94,7 @@ public void filterConditionalNoError() { .assertResult(1); } } + @Test public void filterErrorConditionalNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java b/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java index 4cda73adbc..ab98b5a9f4 100644 --- a/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelFlowableTest.java @@ -454,7 +454,6 @@ public void accept(List<Integer> v) throws Exception { } } - @Test public void collectAsync2() { ExecutorService exec = Executors.newFixedThreadPool(3); @@ -551,7 +550,6 @@ public void accept(List<Integer> v) throws Exception { } } - @Test public void collectAsync3Fused() { ExecutorService exec = Executors.newFixedThreadPool(3); @@ -1100,6 +1098,20 @@ public Flowable<Integer> apply(ParallelFlowable<Integer> pf) throws Exception { .assertResult(1, 2, 3, 4, 5); } + @Test + public void as() { + Flowable.range(1, 5) + .parallel() + .as(new ParallelFlowableConverter<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(ParallelFlowable<Integer> pf) { + return pf.sequential(); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + @Test(expected = TestException.class) public void toThrows() { Flowable.range(1, 5) @@ -1112,6 +1124,18 @@ public Flowable<Integer> apply(ParallelFlowable<Integer> pf) throws Exception { }); } + @Test(expected = TestException.class) + public void asThrows() { + Flowable.range(1, 5) + .parallel() + .as(new ParallelFlowableConverter<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(ParallelFlowable<Integer> pf) { + throw new TestException(); + } + }); + } + @Test public void compose() { Flowable.range(1, 5) diff --git a/src/test/java/io/reactivex/parallel/ParallelFromPublisherTest.java b/src/test/java/io/reactivex/parallel/ParallelFromPublisherTest.java index 874fd62fba..48da2c566f 100644 --- a/src/test/java/io/reactivex/parallel/ParallelFromPublisherTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelFromPublisherTest.java @@ -14,15 +14,22 @@ package io.reactivex.parallel; import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; + import org.junit.Test; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.exceptions.*; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscribers.BasicFuseableSubscriber; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.UnicastProcessor; +import io.reactivex.schedulers.Schedulers; public class ParallelFromPublisherTest { @@ -53,6 +60,53 @@ public void fusedFilterBecomesEmpty() { .assertResult(); } + static final class StripBoundary<T> extends Flowable<T> implements FlowableTransformer<T, T> { + + final Flowable<T> source; + + StripBoundary(Flowable<T> source) { + this.source = source; + } + + @Override + public Publisher<T> apply(Flowable<T> upstream) { + return new StripBoundary<T>(upstream); + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + source.subscribe(new StripBoundarySubscriber<T>(s)); + } + + static final class StripBoundarySubscriber<T> extends BasicFuseableSubscriber<T, T> { + + StripBoundarySubscriber(Subscriber<? super T> downstream) { + super(downstream); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public int requestFusion(int mode) { + QueueSubscription<T> fs = qs; + if (fs != null) { + int m = fs.requestFusion(mode & ~QueueFuseable.BOUNDARY); + this.sourceMode = m; + return m; + } + return QueueFuseable.NONE; + } + + @Override + public T poll() throws Exception { + return qs.poll(); + } + } + } + @Test public void syncFusedMapCrash() { Flowable.just(1) @@ -62,6 +116,7 @@ public Object apply(Integer v) throws Exception { throw new TestException(); } }) + .compose(new StripBoundary<Object>(null)) .parallel() .sequential() .test() @@ -81,6 +136,7 @@ public Object apply(Integer v) throws Exception { throw new TestException(); } }) + .compose(new StripBoundary<Object>(null)) .parallel() .sequential() .test() @@ -88,4 +144,45 @@ public Object apply(Integer v) throws Exception { assertFalse(up.hasSubscribers()); } + + @Test + public void boundaryConfinement() { + final Set<String> between = new HashSet<String>(); + final ConcurrentHashMap<String, String> processing = new ConcurrentHashMap<String, String>(); + + Flowable.range(1, 10) + .observeOn(Schedulers.single(), false, 1) + .doOnNext(new Consumer<Integer>() { + @Override + public void accept(Integer v) throws Exception { + between.add(Thread.currentThread().getName()); + } + }) + .parallel(2, 1) + .runOn(Schedulers.computation(), 1) + .map(new Function<Integer, Object>() { + @Override + public Object apply(Integer v) throws Exception { + processing.putIfAbsent(Thread.currentThread().getName(), ""); + return v; + } + }) + .sequential() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueSet(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + .assertComplete() + .assertNoErrors() + ; + + assertEquals(between.toString(), 1, between.size()); + assertTrue(between.toString(), between.iterator().next().contains("RxSingleScheduler")); + + Map<String, String> map = processing; // AnimalSniffer: CHM.keySet() in Java 8 returns KeySetView + + for (String e : map.keySet()) { + assertTrue(map.toString(), e.contains("RxComputationThreadPool")); + } + } } diff --git a/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java b/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java index bed3f5eae8..09b3dbcf6d 100644 --- a/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelMapTryTest.java @@ -44,6 +44,7 @@ public void mapNoError() { .assertResult(1); } } + @Test public void mapErrorNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { @@ -68,6 +69,7 @@ public void mapConditionalNoError() { .assertResult(1); } } + @Test public void mapErrorConditionalNoError() { for (ParallelFailureHandling e : ParallelFailureHandling.values()) { diff --git a/src/test/java/io/reactivex/parallel/ParallelRunOnTest.java b/src/test/java/io/reactivex/parallel/ParallelRunOnTest.java index bf0bb0b33b..53023b5790 100644 --- a/src/test/java/io/reactivex/parallel/ParallelRunOnTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelRunOnTest.java @@ -163,7 +163,7 @@ public void emptyConditionalBackpressured() { @Test public void nextCancelRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = pp.parallel(1) @@ -192,7 +192,7 @@ public void run() { @SuppressWarnings("unchecked") @Test public void nextCancelRaceBackpressured() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = TestSubscriber.create(0L); @@ -221,7 +221,7 @@ public void run() { @Test public void nextCancelRaceConditional() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = pp.parallel(1) @@ -251,7 +251,7 @@ public void run() { @SuppressWarnings("unchecked") @Test public void nextCancelRaceBackpressuredConditional() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = TestSubscriber.create(0L); diff --git a/src/test/java/io/reactivex/parallel/ParallelSortedJoinTest.java b/src/test/java/io/reactivex/parallel/ParallelSortedJoinTest.java index 99e336d338..aede8c5717 100644 --- a/src/test/java/io/reactivex/parallel/ParallelSortedJoinTest.java +++ b/src/test/java/io/reactivex/parallel/ParallelSortedJoinTest.java @@ -152,7 +152,7 @@ public void asyncDrain() { @Test public void sortCancelRace() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplayProcessor<Integer> pp = ReplayProcessor.create(); pp.onNext(1); pp.onNext(2); @@ -181,7 +181,7 @@ public void run() { @Test public void sortCancelRace2() { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplayProcessor<Integer> pp = ReplayProcessor.create(); pp.onNext(1); pp.onNext(2); diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index a8a998285e..229dc7c873 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -269,7 +269,7 @@ public boolean getAsBoolean() throws Exception { fail("Should have thrown InvocationTargetException(IllegalStateException)"); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof IllegalStateException) { - assertEquals("Plugins can't be changed anymore",ex.getCause().getMessage()); + assertEquals("Plugins can't be changed anymore", ex.getCause().getMessage()); } else { fail("Should have thrown InvocationTargetException(IllegalStateException)"); } @@ -708,12 +708,12 @@ public void flowableStart() { try { RxJavaPlugins.setOnFlowableSubscribe(new BiFunction<Flowable, Subscriber, Subscriber>() { @Override - public Subscriber apply(Flowable o, final Subscriber t) { + public Subscriber apply(Flowable f, final Subscriber t) { return new Subscriber() { @Override - public void onSubscribe(Subscription d) { - t.onSubscribe(d); + public void onSubscribe(Subscription s) { + t.onSubscribe(s); } @SuppressWarnings("unchecked") @@ -1285,7 +1285,6 @@ public Completable apply(Completable completable) throws Exception { } }; - RxJavaPlugins.setInitComputationSchedulerHandler(callable2scheduler); RxJavaPlugins.setComputationSchedulerHandler(scheduler2scheduler); RxJavaPlugins.setIoSchedulerHandler(scheduler2scheduler); @@ -1369,8 +1368,6 @@ public void subscribeActual(CompletableObserver t) { assertNull(RxJavaPlugins.onAssembly((Maybe)null)); - assertNull(RxJavaPlugins.onSchedule(null)); - Maybe myb = new Maybe() { @Override public void subscribeActual(MaybeObserver t) { @@ -1380,11 +1377,7 @@ public void subscribeActual(MaybeObserver t) { assertSame(myb, RxJavaPlugins.onAssembly(myb)); - - assertNull(RxJavaPlugins.onSchedule(null)); - Runnable action = Functions.EMPTY_RUNNABLE; - assertSame(action, RxJavaPlugins.onSchedule(action)); class AllSubscriber implements Subscriber, Observer, SingleObserver, CompletableObserver, MaybeObserver { @@ -1959,7 +1952,6 @@ public Subscriber apply(Flowable f, Subscriber s) throws Exception { } } - @SuppressWarnings("rawtypes") @Test public void maybeCreate() { @@ -2078,7 +2070,7 @@ public void run() { Thread t = value.get(); assertNotNull(t); - assertTrue(expectedThreadName.equals(t.getName())); + assertEquals(expectedThreadName, t.getName()); } catch (Exception e) { fail(); } finally { diff --git a/src/test/java/io/reactivex/processors/AsyncProcessorTest.java b/src/test/java/io/reactivex/processors/AsyncProcessorTest.java index 847d363580..3d85d97eee 100644 --- a/src/test/java/io/reactivex/processors/AsyncProcessorTest.java +++ b/src/test/java/io/reactivex/processors/AsyncProcessorTest.java @@ -13,29 +13,24 @@ package io.reactivex.processors; -import io.reactivex.TestHelper; -import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Consumer; -import io.reactivex.internal.fuseable.QueueSubscription; -import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subscribers.SubscriberFusion; -import io.reactivex.subscribers.TestSubscriber; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; -import org.reactivestreams.Subscriber; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import org.junit.*; +import org.mockito.*; +import org.reactivestreams.Subscriber; + +import io.reactivex.TestHelper; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.fuseable.QueueFuseable; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.subscribers.*; -public class AsyncProcessorTest extends DelayedFlowableProcessorTest<Object> { +public class AsyncProcessorTest extends FlowableProcessorTest<Object> { private final Throwable testException = new Throwable(); @@ -46,149 +41,149 @@ protected FlowableProcessor<Object> create() { @Test public void testNeverCompleted() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); - verify(observer, Mockito.never()).onNext(anyString()); - verify(observer, Mockito.never()).onError(testException); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, Mockito.never()).onNext(anyString()); + verify(subscriber, Mockito.never()).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testCompleted() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onComplete(); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test @Ignore("Null values not allowed") public void testNull() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext(null); - subject.onComplete(); + processor.onNext(null); + processor.onComplete(); - verify(observer, times(1)).onNext(null); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext(null); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSubscribeAfterCompleted() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onComplete(); - subject.subscribe(observer); + processor.subscribe(subscriber); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSubscribeAfterError() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); RuntimeException re = new RuntimeException("failed"); - subject.onError(re); + processor.onError(re); - subject.subscribe(observer); + processor.subscribe(subscriber); - verify(observer, times(1)).onError(re); - verify(observer, Mockito.never()).onNext(any(String.class)); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, times(1)).onError(re); + verify(subscriber, Mockito.never()).onNext(any(String.class)); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testError() { - AsyncProcessor<String> subject = AsyncProcessor.create(); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); - - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onError(testException); - subject.onNext("four"); - subject.onError(new Throwable()); - subject.onComplete(); - - verify(observer, Mockito.never()).onNext(anyString()); - verify(observer, times(1)).onError(testException); - verify(observer, Mockito.never()).onComplete(); + AsyncProcessor<String> processor = AsyncProcessor.create(); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); + + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onError(testException); + processor.onNext("four"); + processor.onError(new Throwable()); + processor.onComplete(); + + verify(subscriber, Mockito.never()).onNext(anyString()); + verify(subscriber, times(1)).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testUnsubscribeBeforeCompleted() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - subject.subscribe(ts); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + processor.subscribe(ts); - subject.onNext("one"); - subject.onNext("two"); + processor.onNext("one"); + processor.onNext("two"); ts.dispose(); - verify(observer, Mockito.never()).onNext(anyString()); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, Mockito.never()).onNext(anyString()); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, Mockito.never()).onComplete(); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("three"); + processor.onComplete(); - verify(observer, Mockito.never()).onNext(anyString()); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, Mockito.never()).onNext(anyString()); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testEmptySubjectCompleted() { - AsyncProcessor<String> subject = AsyncProcessor.create(); + AsyncProcessor<String> processor = AsyncProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onComplete(); + processor.onComplete(); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, never()).onNext(null); - inOrder.verify(observer, never()).onNext(any(String.class)); - inOrder.verify(observer, times(1)).onComplete(); + InOrder inOrder = inOrder(subscriber); + inOrder.verify(subscriber, never()).onNext(null); + inOrder.verify(subscriber, never()).onNext(any(String.class)); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -204,10 +199,10 @@ public void testSubscribeCompletionRaceCondition() { * With the synchronization code in place I can not get this to fail on my laptop. */ for (int i = 0; i < 50; i++) { - final AsyncProcessor<String> subject = AsyncProcessor.create(); + final AsyncProcessor<String> processor = AsyncProcessor.create(); final AtomicReference<String> value1 = new AtomicReference<String>(); - subject.subscribe(new Consumer<String>() { + processor.subscribe(new Consumer<String>() { @Override public void accept(String t1) { @@ -226,15 +221,15 @@ public void accept(String t1) { @Override public void run() { - subject.onNext("value"); - subject.onComplete(); + processor.onNext("value"); + processor.onComplete(); } }); - SubjectSubscriberThread t2 = new SubjectSubscriberThread(subject); - SubjectSubscriberThread t3 = new SubjectSubscriberThread(subject); - SubjectSubscriberThread t4 = new SubjectSubscriberThread(subject); - SubjectSubscriberThread t5 = new SubjectSubscriberThread(subject); + SubjectSubscriberThread t2 = new SubjectSubscriberThread(processor); + SubjectSubscriberThread t3 = new SubjectSubscriberThread(processor); + SubjectSubscriberThread t4 = new SubjectSubscriberThread(processor); + SubjectSubscriberThread t5 = new SubjectSubscriberThread(processor); t2.start(); t3.start(); @@ -262,18 +257,18 @@ public void run() { private static class SubjectSubscriberThread extends Thread { - private final AsyncProcessor<String> subject; + private final AsyncProcessor<String> processor; private final AtomicReference<String> value = new AtomicReference<String>(); - SubjectSubscriberThread(AsyncProcessor<String> subject) { - this.subject = subject; + SubjectSubscriberThread(AsyncProcessor<String> processor) { + this.processor = processor; } @Override public void run() { try { // a timeout exception will happen if we don't get a terminal state - String v = subject.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); + String v = processor.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); value.set(v); } catch (Exception e) { e.printStackTrace(); @@ -300,7 +295,6 @@ public void run() { // assertEquals(1, ts.getOnErrorEvents().size()); // } - // FIXME subscriber methods are not allowed to throw // /** // * This one has multiple failures so should get a CompositeException @@ -372,6 +366,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { AsyncProcessor<Object> as = AsyncProcessor.create(); @@ -395,13 +390,13 @@ public void testCurrentStateMethodsError() { public void fusionLive() { AsyncProcessor<Integer> ap = new AsyncProcessor<Integer>(); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); ap.subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)); + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)); ts.assertNoValues().assertNoErrors().assertNotComplete(); @@ -420,13 +415,13 @@ public void fusionOfflie() { ap.onNext(1); ap.onComplete(); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); ap.subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1); } @@ -467,7 +462,7 @@ public void cancelUpfront() { public void cancelRace() { AsyncProcessor<Object> p = AsyncProcessor.create(); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Object> ts1 = p.test(); final TestSubscriber<Object> ts2 = p.test(); @@ -485,14 +480,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void onErrorCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AsyncProcessor<Object> p = AsyncProcessor.create(); final TestSubscriber<Object> ts1 = p.test(); @@ -513,7 +508,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (ts1.errorCount() != 0) { ts1.assertFailure(TestException.class); diff --git a/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java b/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java index 8a1766f724..d465aa7a56 100644 --- a/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java +++ b/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java @@ -13,33 +13,27 @@ package io.reactivex.processors; -import io.reactivex.Flowable; -import io.reactivex.Scheduler; -import io.reactivex.TestHelper; -import io.reactivex.exceptions.MissingBackpressureException; -import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; -import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subscribers.DefaultSubscriber; -import io.reactivex.subscribers.TestSubscriber; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; -import org.reactivestreams.Subscriber; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import org.junit.*; +import org.mockito.*; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.BehaviorProcessor.BehaviorSubscription; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.*; -public class BehaviorProcessorTest extends DelayedFlowableProcessorTest<Object> { +public class BehaviorProcessorTest extends FlowableProcessorTest<Object> { private final Throwable testException = new Throwable(); @@ -50,88 +44,88 @@ protected FlowableProcessor<Object> create() { @Test public void testThatSubscriberReceivesDefaultValueAndSubsequentEvents() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); - verify(observer, times(1)).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(testException); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, times(1)).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testThatSubscriberReceivesLatestAndThenSubsequentEvents() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); - subject.onNext("one"); + processor.onNext("one"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("two"); - subject.onNext("three"); + processor.onNext("two"); + processor.onNext("three"); - verify(observer, Mockito.never()).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(testException); - verify(observer, Mockito.never()).onComplete(); + verify(subscriber, Mockito.never()).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testSubscribeThenOnComplete() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onComplete(); + processor.onNext("one"); + processor.onComplete(); - verify(observer, times(1)).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSubscribeToCompletedOnlyEmitsOnComplete() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); - subject.onNext("one"); - subject.onComplete(); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); + processor.onNext("one"); + processor.onComplete(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - verify(observer, never()).onNext("default"); - verify(observer, never()).onNext("one"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, never()).onNext("default"); + verify(subscriber, never()).onNext("one"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testSubscribeToErrorOnlyEmitsOnError() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); - subject.onNext("one"); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); + processor.onNext("one"); RuntimeException re = new RuntimeException("test error"); - subject.onError(re); + processor.onError(re); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - verify(observer, never()).onNext("default"); - verify(observer, never()).onNext("one"); - verify(observer, times(1)).onError(re); - verify(observer, never()).onComplete(); + verify(subscriber, never()).onNext("default"); + verify(subscriber, never()).onNext("one"); + verify(subscriber, times(1)).onError(re); + verify(subscriber, never()).onComplete(); } @Test @@ -181,71 +175,71 @@ public void testCompletedStopsEmittingData() { @Test public void testCompletedAfterErrorIsNotSent() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onError(testException); - subject.onNext("two"); - subject.onComplete(); + processor.onNext("one"); + processor.onError(testException); + processor.onNext("two"); + processor.onComplete(); - verify(observer, times(1)).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onError(testException); - verify(observer, never()).onNext("two"); - verify(observer, never()).onComplete(); + verify(subscriber, times(1)).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onError(testException); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onComplete(); } @Test public void testCompletedAfterErrorIsNotSent2() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); - - subject.onNext("one"); - subject.onError(testException); - subject.onNext("two"); - subject.onComplete(); - - verify(observer, times(1)).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onError(testException); - verify(observer, never()).onNext("two"); - verify(observer, never()).onComplete(); - - Subscriber<Object> o2 = TestHelper.mockSubscriber(); - subject.subscribe(o2); - verify(o2, times(1)).onError(testException); - verify(o2, never()).onNext(any()); - verify(o2, never()).onComplete(); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); + + processor.onNext("one"); + processor.onError(testException); + processor.onNext("two"); + processor.onComplete(); + + verify(subscriber, times(1)).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onError(testException); + verify(subscriber, never()).onNext("two"); + verify(subscriber, never()).onComplete(); + + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); + processor.subscribe(subscriber2); + verify(subscriber2, times(1)).onError(testException); + verify(subscriber2, never()).onNext(any()); + verify(subscriber2, never()).onComplete(); } @Test public void testCompletedAfterErrorIsNotSent3() { - BehaviorProcessor<String> subject = BehaviorProcessor.createDefault("default"); - - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); - - subject.onNext("one"); - subject.onComplete(); - subject.onNext("two"); - subject.onComplete(); - - verify(observer, times(1)).onNext("default"); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onComplete(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, never()).onNext("two"); - - Subscriber<Object> o2 = TestHelper.mockSubscriber(); - subject.subscribe(o2); - verify(o2, times(1)).onComplete(); - verify(o2, never()).onNext(any()); - verify(observer, never()).onError(any(Throwable.class)); + BehaviorProcessor<String> processor = BehaviorProcessor.createDefault("default"); + + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); + + processor.onNext("one"); + processor.onComplete(); + processor.onNext("two"); + processor.onComplete(); + + verify(subscriber, times(1)).onNext("default"); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext("two"); + + Subscriber<Object> subscriber2 = TestHelper.mockSubscriber(); + processor.subscribe(subscriber2); + verify(subscriber2, times(1)).onComplete(); + verify(subscriber2, never()).onNext(any()); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test(timeout = 1000) @@ -253,8 +247,8 @@ public void testUnsubscriptionCase() { BehaviorProcessor<String> src = BehaviorProcessor.createDefault("null"); // FIXME was plain null which is not allowed for (int i = 0; i < 10; i++) { - final Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); String v = "" + i; src.onNext(v); System.out.printf("Turn: %d%n", i); @@ -269,34 +263,35 @@ public Flowable<String> apply(String t1) { .subscribe(new DefaultSubscriber<String>() { @Override public void onNext(String t) { - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); - inOrder.verify(o).onNext(v + ", " + v); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(v + ", " + v); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testStartEmpty() { BehaviorProcessor<Integer> source = BehaviorProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); - source.subscribe(o); + source.subscribe(subscriber); - inOrder.verify(o, never()).onNext(any()); - inOrder.verify(o, never()).onComplete(); + inOrder.verify(subscriber, never()).onNext(any()); + inOrder.verify(subscriber, never()).onComplete(); source.onNext(1); @@ -304,63 +299,63 @@ public void testStartEmpty() { source.onNext(2); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); - inOrder.verify(o).onNext(1); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - - } + @Test public void testStartEmptyThenAddOne() { BehaviorProcessor<Integer> source = BehaviorProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); source.onNext(1); - source.subscribe(o); + source.subscribe(subscriber); - inOrder.verify(o).onNext(1); + inOrder.verify(subscriber).onNext(1); source.onComplete(); source.onNext(2); - inOrder.verify(o).onComplete(); + inOrder.verify(subscriber).onComplete(); inOrder.verifyNoMoreInteractions(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onError(any(Throwable.class)); } + @Test public void testStartEmptyCompleteWithOne() { BehaviorProcessor<Integer> source = BehaviorProcessor.create(); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); source.onNext(1); source.onComplete(); source.onNext(2); - source.subscribe(o); + source.subscribe(subscriber); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); - verify(o, never()).onNext(any()); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(any()); } @Test public void testTakeOneSubscriber() { BehaviorProcessor<Integer> source = BehaviorProcessor.createDefault(1); - final Subscriber<Object> o = TestHelper.mockSubscriber(); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); - source.take(1).subscribe(o); + source.take(1).subscribe(subscriber); - verify(o).onNext(1); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); assertEquals(0, source.subscriberCount()); assertFalse(source.hasSubscribers()); @@ -369,7 +364,7 @@ public void testTakeOneSubscriber() { // FIXME RS subscribers are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { -// BehaviorSubject<String> ps = BehaviorSubject.create(); +// BehaviorProcessor<String> ps = BehaviorProcessor.create(); // // ps.subscribe(); // TestSubscriber<String> ts = new TestSubscriber<String>(); @@ -391,7 +386,7 @@ public void testTakeOneSubscriber() { // */ // @Test // public void testOnErrorThrowsDoesntPreventDelivery2() { -// BehaviorSubject<String> ps = BehaviorSubject.create(); +// BehaviorProcessor<String> ps = BehaviorProcessor.create(); // // ps.subscribe(); // ps.subscribe(); @@ -411,6 +406,7 @@ public void testTakeOneSubscriber() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } + @Test public void testEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -555,6 +551,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { BehaviorProcessor<Object> as = BehaviorProcessor.create(); @@ -636,7 +633,7 @@ public void cancelOnArrival2() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorProcessor<Object> p = BehaviorProcessor.create(); final TestSubscriber<Object> ts = p.test(); @@ -655,14 +652,14 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void subscribeOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorProcessor<Object> p = BehaviorProcessor.createDefault((Object)1); final TestSubscriber[] ts = { null }; @@ -681,7 +678,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); if (ts[0].valueCount() == 1) { ts[0].assertValue(2).assertNoErrors().assertNotComplete(); @@ -759,7 +756,7 @@ public void run() { @Test public void completeSubscribeRace() throws Exception { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorProcessor<Object> p = BehaviorProcessor.create(); final TestSubscriber<Object> ts = new TestSubscriber<Object>(); @@ -786,7 +783,7 @@ public void run() { @Test public void errorSubscribeRace() throws Exception { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorProcessor<Object> p = BehaviorProcessor.create(); final TestSubscriber<Object> ts = new TestSubscriber<Object>(); @@ -812,4 +809,162 @@ public void run() { ts.assertFailure(TestException.class); } } + + @Test(timeout = 10000) + public void subscriberCancelOfferRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final BehaviorProcessor<Integer> pp = BehaviorProcessor.create(); + + final TestSubscriber<Integer> ts = pp.test(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 2; i++) { + while (!pp.offer(i)) { } + } + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + + if (ts.valueCount() > 0) { + ts.assertValuesOnly(0); + } else { + ts.assertEmpty(); + } + } + } + + @Test + public void behaviorDisposableDisposeState() { + BehaviorProcessor<Integer> bp = BehaviorProcessor.create(); + bp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + BehaviorSubscription<Integer> bs = new BehaviorSubscription<Integer>(ts, bp); + ts.onSubscribe(bs); + + assertFalse(bs.cancelled); + + bs.cancel(); + + assertTrue(bs.cancelled); + + bs.cancel(); + + assertTrue(bs.cancelled); + + assertTrue(bs.test(2)); + + bs.emitFirst(); + + ts.assertEmpty(); + + bs.emitNext(2, 0); + } + + @Test + public void emitFirstDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + BehaviorProcessor<Integer> bp = BehaviorProcessor.create(); + bp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + final BehaviorSubscription<Integer> bs = new BehaviorSubscription<Integer>(ts, bp); + ts.onSubscribe(bs); + + Runnable r1 = new Runnable() { + @Override + public void run() { + bs.emitFirst(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + bs.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void emitNextDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + BehaviorProcessor<Integer> bp = BehaviorProcessor.create(); + bp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + final BehaviorSubscription<Integer> bs = new BehaviorSubscription<Integer>(ts, bp); + ts.onSubscribe(bs); + + Runnable r1 = new Runnable() { + @Override + public void run() { + bs.emitNext(2, 0); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + bs.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void emittingEmitNext() { + BehaviorProcessor<Integer> bp = BehaviorProcessor.create(); + bp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + final BehaviorSubscription<Integer> bs = new BehaviorSubscription<Integer>(ts, bp); + ts.onSubscribe(bs); + + bs.emitting = true; + bs.emitNext(2, 1); + bs.emitNext(3, 2); + + assertNotNull(bs.queue); + } + + @Test + public void badRequest() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + BehaviorProcessor<Integer> bp = BehaviorProcessor.create(); + bp.onNext(1); + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + final BehaviorSubscription<Integer> bs = new BehaviorSubscription<Integer>(ts, bp); + ts.onSubscribe(bs); + + bs.request(-1); + + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); + } + } + } diff --git a/src/test/java/io/reactivex/processors/DelayedFlowableProcessorTest.java b/src/test/java/io/reactivex/processors/DelayedFlowableProcessorTest.java deleted file mode 100644 index 484335870d..0000000000 --- a/src/test/java/io/reactivex/processors/DelayedFlowableProcessorTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2016-present, RxJava Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is - * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the specific language governing permissions and limitations under the License. - */ - -package io.reactivex.processors; - -import io.reactivex.subscribers.TestSubscriber; -import org.junit.Test; - -import static org.junit.Assert.assertFalse; - -public abstract class DelayedFlowableProcessorTest<T> extends FlowableProcessorTest<T> { - - @Test - public void onNextNullDelayed() { - final FlowableProcessor<T> p = create(); - - TestSubscriber<T> ts = p.test(); - - p.onNext(null); - - - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNullDelayed() { - final FlowableProcessor<T> p = create(); - - TestSubscriber<T> ts = p.test(); - - p.onError(null); - assertFalse(p.hasSubscribers()); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } -} diff --git a/src/test/java/io/reactivex/processors/FlowableProcessorTest.java b/src/test/java/io/reactivex/processors/FlowableProcessorTest.java index bd37deb282..71946677ed 100644 --- a/src/test/java/io/reactivex/processors/FlowableProcessorTest.java +++ b/src/test/java/io/reactivex/processors/FlowableProcessorTest.java @@ -13,6 +13,8 @@ package io.reactivex.processors; +import static org.junit.Assert.*; + import org.junit.Test; public abstract class FlowableProcessorTest<T> { @@ -21,25 +23,29 @@ public abstract class FlowableProcessorTest<T> { @Test public void onNextNull() { - final FlowableProcessor<T> p = create(); + FlowableProcessor<T> p = create(); - p.onNext(null); + try { + p.onNext(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onNext called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } - p.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + p.test().assertEmpty().cancel(); } @Test public void onErrorNull() { - final FlowableProcessor<T> p = create(); + FlowableProcessor<T> p = create(); - p.onError(null); + try { + p.onError(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onError called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } - p.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + p.test().assertEmpty().cancel(); } } diff --git a/src/test/java/io/reactivex/processors/MulticastProcessorTest.java b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java new file mode 100644 index 0000000000..d541dffb3e --- /dev/null +++ b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java @@ -0,0 +1,823 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.processors; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.TestSubscriber; + +public class MulticastProcessorTest { + + @Test + public void complete() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + + assertFalse(mp.hasSubscribers()); + assertFalse(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + TestSubscriber<Integer> ts = mp.test(); + + assertTrue(mp.hasSubscribers()); + assertFalse(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + mp.onNext(1); + mp.onComplete(); + + ts.assertResult(1); + + assertFalse(mp.hasSubscribers()); + assertTrue(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + mp.test().assertResult(); + } + + @Test + public void error() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + + assertFalse(mp.hasSubscribers()); + assertFalse(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + TestSubscriber<Integer> ts = mp.test(); + + assertTrue(mp.hasSubscribers()); + assertFalse(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + mp.onNext(1); + mp.onError(new IOException()); + + ts.assertFailure(IOException.class, 1); + + assertFalse(mp.hasSubscribers()); + assertFalse(mp.hasComplete()); + assertTrue(mp.hasThrowable()); + assertNotNull(mp.getThrowable()); + assertTrue("" + mp.getThrowable(), mp.getThrowable() instanceof IOException); + + mp.test().assertFailure(IOException.class); + } + + @Test + public void overflow() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(1); + mp.start(); + + TestSubscriber<Integer> ts = mp.test(0); + + assertTrue(mp.offer(1)); + assertFalse(mp.offer(2)); + + mp.onNext(3); + + ts.assertEmpty(); + + ts.request(1); + + ts.assertFailure(MissingBackpressureException.class, 1); + + mp.test().assertFailure(MissingBackpressureException.class); + } + + @Test + public void backpressure() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16, false); + mp.start(); + + for (int i = 0; i < 10; i++) { + mp.onNext(i); + } + mp.onComplete(); + + mp.test(0) + .assertEmpty() + .requestMore(1) + .assertValuesOnly(0) + .requestMore(2) + .assertValuesOnly(0, 1, 2) + .requestMore(3) + .assertValuesOnly(0, 1, 2, 3, 4, 5) + .requestMore(4) + .assertResult(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Test + public void refCounted() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + BooleanSubscription bs = new BooleanSubscription(); + + mp.onSubscribe(bs); + + assertFalse(bs.isCancelled()); + + mp.test().cancel(); + + assertTrue(bs.isCancelled()); + + assertFalse(mp.hasSubscribers()); + assertTrue(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + mp.test().assertResult(); + } + + @Test + public void refCounted2() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16, true); + BooleanSubscription bs = new BooleanSubscription(); + + mp.onSubscribe(bs); + + assertFalse(bs.isCancelled()); + + mp.test(1, true); + + assertTrue(bs.isCancelled()); + + assertFalse(mp.hasSubscribers()); + assertTrue(mp.hasComplete()); + assertFalse(mp.hasThrowable()); + assertNull(mp.getThrowable()); + + mp.test().assertResult(); + } + + @Test + public void longRunning() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16); + Flowable.range(1, 1000).subscribe(mp); + + mp.test().assertValueCount(1000).assertNoErrors().assertComplete(); + } + + @Test + public void oneByOne() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16); + Flowable.range(1, 1000).subscribe(mp); + + mp + .rebatchRequests(1) + .test() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void take() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16); + Flowable.range(1, 1000).subscribe(mp); + + mp.take(10).test().assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void takeRefCount() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16, true); + Flowable.range(1, 1000).subscribe(mp); + + mp.take(10).test().assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void takeRefCountExact() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(16, true); + Flowable.range(1, 10).subscribe(mp); + + mp + .rebatchRequests(10) + .take(10) + .test().assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void crossCancel() { + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ts1.cancel(); + ts1.onComplete(); + } + }; + + MulticastProcessor<Integer> mp = MulticastProcessor.create(false); + + mp.subscribe(ts2); + mp.subscribe(ts1); + + mp.start(); + + mp.onNext(1); + mp.onComplete(); + + ts1.assertResult(); + ts2.assertResult(1); + } + + @Test + public void crossCancelError() { + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>() { + @Override + public void onError(Throwable t) { + super.onError(t); + ts1.cancel(); + ts1.onComplete(); + } + }; + + MulticastProcessor<Integer> mp = MulticastProcessor.create(false); + + mp.subscribe(ts2); + mp.subscribe(ts1); + + mp.start(); + + mp.onNext(1); + mp.onError(new IOException()); + + ts1.assertResult(1); + ts2.assertFailure(IOException.class, 1); + } + + @Test + public void crossCancelComplete() { + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>() { + @Override + public void onComplete() { + super.onComplete(); + ts1.cancel(); + ts1.onNext(2); + ts1.onComplete(); + } + }; + + MulticastProcessor<Integer> mp = MulticastProcessor.create(false); + + mp.subscribe(ts2); + mp.subscribe(ts1); + + mp.start(); + + mp.onNext(1); + mp.onComplete(); + + ts1.assertResult(1, 2); + ts2.assertResult(1); + } + + @Test + public void crossCancel1() { + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(1); + + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + ts1.cancel(); + ts1.onComplete(); + } + }; + + MulticastProcessor<Integer> mp = MulticastProcessor.create(false); + + mp.subscribe(ts2); + mp.subscribe(ts1); + + mp.start(); + + mp.onNext(1); + mp.onComplete(); + + ts1.assertResult(); + ts2.assertResult(1); + } + + @Test + public void requestCancel() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + MulticastProcessor<Integer> mp = MulticastProcessor.create(false); + + mp.subscribe(new FlowableSubscriber<Integer>() { + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription t) { + t.request(-1); + t.request(1); + t.request(Long.MAX_VALUE); + t.request(Long.MAX_VALUE); + t.cancel(); + t.cancel(); + t.request(2); + } + }); + + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void unbounded() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(4, false); + mp.startUnbounded(); + + for (int i = 0; i < 10; i++) { + assertTrue(mp.offer(i)); + } + mp.onComplete(); + + mp.test().assertResult(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Test + public void multiStart() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + MulticastProcessor<Integer> mp = MulticastProcessor.create(4, false); + + mp.start(); + mp.start(); + mp.startUnbounded(); + BooleanSubscription bs = new BooleanSubscription(); + mp.onSubscribe(bs); + + assertTrue(bs.isCancelled()); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertError(errors, 1, ProtocolViolationException.class); + TestHelper.assertError(errors, 2, ProtocolViolationException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test(expected = NullPointerException.class) + public void onNextNull() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(4, false); + mp.start(); + mp.onNext(null); + } + + @Test(expected = NullPointerException.class) + public void onOfferNull() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(4, false); + mp.start(); + mp.offer(null); + } + + @Test(expected = NullPointerException.class) + public void onErrorNull() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(4, false); + mp.start(); + mp.onError(null); + } + + @Test + public void afterTerminated() { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + mp.onComplete(); + mp.onComplete(); + mp.onError(new IOException()); + mp.onNext(1); + mp.offer(1); + + mp.test().assertResult(); + + TestHelper.assertUndeliverable(errors, 0, IOException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void asyncFused() { + UnicastProcessor<Integer> up = UnicastProcessor.create(); + MulticastProcessor<Integer> mp = MulticastProcessor.create(4); + + up.subscribe(mp); + + TestSubscriber<Integer> ts = mp.test(); + + for (int i = 0; i < 10; i++) { + up.onNext(i); + } + + assertFalse(mp.offer(10)); + + up.onComplete(); + + ts.assertResult(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + + @Test + public void fusionCrash() { + MulticastProcessor<Integer> mp = Flowable.range(1, 5) + .map(new Function<Integer, Integer>() { + @Override + public Integer apply(Integer v) throws Exception { + throw new IOException(); + } + }) + .subscribeWith(MulticastProcessor.<Integer>create()); + + mp.test().assertFailure(IOException.class); + } + + @Test + public void lockstep() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + TestSubscriber<Integer> ts1 = mp.test(); + mp.start(); + + mp.onNext(1); + mp.onNext(2); + + ts1.assertValues(1, 2); + + TestSubscriber<Integer> ts2 = mp.test(0); + + ts2.assertEmpty(); + + mp.onNext(3); + + ts1.assertValues(1, 2); + ts2.assertEmpty(); + + mp.onComplete(); + + ts1.assertValues(1, 2); + ts2.assertEmpty(); + + ts2.request(1); + + ts1.assertResult(1, 2, 3); + ts2.assertResult(3); + } + + @Test + public void rejectedFusion() { + + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + TestHelper.<Integer>rejectFlowableFusion() + .subscribe(mp); + + mp.test().assertEmpty(); + } + + @Test + public void addRemoveRaceNoRefCount() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + final TestSubscriber<Integer> ts = mp.test(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + mp.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2); + + assertTrue(mp.hasSubscribers()); + } + } + + @Test + public void addRemoveRaceNoRefCountNonEmpty() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + mp.test(); + final TestSubscriber<Integer> ts = mp.test(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + mp.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2); + + assertTrue(mp.hasSubscribers()); + } + } + + @Test + public void addRemoveRaceWitRefCount() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + + final TestSubscriber<Integer> ts = mp.test(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + mp.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void cancelUpfront() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + mp.test(0, true).assertEmpty(); + + assertFalse(mp.hasSubscribers()); + } + + @Test + public void cancelUpfrontOtherConsumersPresent() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + mp.test(); + + mp.test(0, true).assertEmpty(); + + assertTrue(mp.hasSubscribers()); + } + + @Test + public void consumerRequestRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + mp.startUnbounded(); + mp.onNext(1); + mp.onNext(2); + + final TestSubscriber<Integer> ts = mp.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertValuesOnly(1, 2); + } + } + + @Test + public void consumerUpstreamRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + + final Flowable<Integer> source = Flowable.range(1, 5); + + final TestSubscriber<Integer> ts = mp.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.request(5); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(mp); + } + }; + + TestHelper.race(r1, r2); + + ts + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1, 2, 3, 4, 5); + } + } + + @Test + public void emitCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + mp.startUnbounded(); + + final TestSubscriber<Integer> ts = mp.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + mp.onNext(1); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void cancelCancelDrain() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + + final TestSubscriber<Integer> ts1 = mp.test(); + final TestSubscriber<Integer> ts2 = mp.test(); + + mp.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts2.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void requestCancelRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + + final TestSubscriber<Integer> ts1 = mp.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts1.request(1); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void noUpstream() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + + TestSubscriber<Integer> ts = mp.test(0); + + ts.request(1); + + assertTrue(mp.hasSubscribers()); + } + + @Test + public void requestUpstreamPrefetchNonFused() { + for (int j = 1; j < 12; j++) { + MulticastProcessor<Integer> mp = MulticastProcessor.create(j, true); + + TestSubscriber<Integer> ts = mp.test(0).withTag("Prefetch: " + j); + + Flowable.range(1, 10).hide().subscribe(mp); + + ts.assertEmpty() + .requestMore(3) + .assertValuesOnly(1, 2, 3) + .requestMore(3) + .assertValuesOnly(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void requestUpstreamPrefetchNonFused2() { + for (int j = 1; j < 12; j++) { + MulticastProcessor<Integer> mp = MulticastProcessor.create(j, true); + + TestSubscriber<Integer> ts = mp.test(0).withTag("Prefetch: " + j); + + Flowable.range(1, 10).hide().subscribe(mp); + + ts.assertEmpty() + .requestMore(2) + .assertValuesOnly(1, 2) + .requestMore(2) + .assertValuesOnly(1, 2, 3, 4) + .requestMore(6) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } +} diff --git a/src/test/java/io/reactivex/processors/PublishProcessorTest.java b/src/test/java/io/reactivex/processors/PublishProcessorTest.java index 3078250ac0..980ed84187 100644 --- a/src/test/java/io/reactivex/processors/PublishProcessorTest.java +++ b/src/test/java/io/reactivex/processors/PublishProcessorTest.java @@ -14,7 +14,6 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; @@ -40,24 +39,24 @@ protected FlowableProcessor<Object> create() { @Test public void testCompleted() { - PublishProcessor<String> subject = PublishProcessor.create(); + PublishProcessor<String> processor = PublishProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onComplete(); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); - subject.onNext("four"); - subject.onComplete(); - subject.onError(new Throwable()); + processor.onNext("four"); + processor.onComplete(); + processor.onError(new Throwable()); - assertCompletedSubscriber(observer); + assertCompletedSubscriber(subscriber); // todo bug? assertNeverSubscriber(anotherSubscriber); } @@ -103,105 +102,105 @@ public void testCompletedStopsEmittingData() { inOrderC.verifyNoMoreInteractions(); } - private void assertCompletedSubscriber(Subscriber<String> observer) { - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + private void assertCompletedSubscriber(Subscriber<String> subscriber) { + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testError() { - PublishProcessor<String> subject = PublishProcessor.create(); + PublishProcessor<String> processor = PublishProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onError(testException); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onError(testException); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); - subject.onNext("four"); - subject.onError(new Throwable()); - subject.onComplete(); + processor.onNext("four"); + processor.onError(new Throwable()); + processor.onComplete(); - assertErrorSubscriber(observer); + assertErrorSubscriber(subscriber); // todo bug? assertNeverSubscriber(anotherSubscriber); } - private void assertErrorSubscriber(Subscriber<String> observer) { - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, times(1)).onError(testException); - verify(observer, Mockito.never()).onComplete(); + private void assertErrorSubscriber(Subscriber<String> subscriber) { + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, times(1)).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testSubscribeMidSequence() { - PublishProcessor<String> subject = PublishProcessor.create(); + PublishProcessor<String> processor = PublishProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); + processor.onNext("one"); + processor.onNext("two"); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("three"); + processor.onComplete(); - assertCompletedSubscriber(observer); + assertCompletedSubscriber(subscriber); assertCompletedStartingWithThreeSubscriber(anotherSubscriber); } - private void assertCompletedStartingWithThreeSubscriber(Subscriber<String> observer) { - verify(observer, Mockito.never()).onNext("one"); - verify(observer, Mockito.never()).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + private void assertCompletedStartingWithThreeSubscriber(Subscriber<String> subscriber) { + verify(subscriber, Mockito.never()).onNext("one"); + verify(subscriber, Mockito.never()).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); } @Test public void testUnsubscribeFirstSubscriber() { - PublishProcessor<String> subject = PublishProcessor.create(); + PublishProcessor<String> processor = PublishProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - subject.subscribe(ts); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + processor.subscribe(ts); - subject.onNext("one"); - subject.onNext("two"); + processor.onNext("one"); + processor.onNext("two"); ts.dispose(); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("three"); + processor.onComplete(); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); assertCompletedStartingWithThreeSubscriber(anotherSubscriber); } - private void assertObservedUntilTwo(Subscriber<String> observer) { - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, Mockito.never()).onComplete(); + private void assertObservedUntilTwo(Subscriber<String> subscriber) { + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, Mockito.never()).onComplete(); } @Test @@ -220,7 +219,7 @@ public void testNestedSubscribe() { public Flowable<String> apply(final Integer v) { countParent.incrementAndGet(); - // then subscribe to subject again (it will not receive the previous value) + // then subscribe to processor again (it will not receive the previous value) return s.map(new Function<Integer, String>() { @Override @@ -260,36 +259,36 @@ public void accept(String v) { */ @Test public void testReSubscribe() { - final PublishProcessor<Integer> ps = PublishProcessor.create(); + final PublishProcessor<Integer> pp = PublishProcessor.create(); - Subscriber<Integer> o1 = TestHelper.mockSubscriber(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(o1); - ps.subscribe(ts); + Subscriber<Integer> subscriber1 = TestHelper.mockSubscriber(); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(subscriber1); + pp.subscribe(ts); // emit - ps.onNext(1); + pp.onNext(1); // validate we got it - InOrder inOrder1 = inOrder(o1); - inOrder1.verify(o1, times(1)).onNext(1); + InOrder inOrder1 = inOrder(subscriber1); + inOrder1.verify(subscriber1, times(1)).onNext(1); inOrder1.verifyNoMoreInteractions(); // unsubscribe ts.dispose(); // emit again but nothing will be there to receive it - ps.onNext(2); + pp.onNext(2); - Subscriber<Integer> o2 = TestHelper.mockSubscriber(); - TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(o2); - ps.subscribe(ts2); + Subscriber<Integer> subscriber2 = TestHelper.mockSubscriber(); + TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(subscriber2); + pp.subscribe(ts2); // emit - ps.onNext(3); + pp.onNext(3); // validate we got it - InOrder inOrder2 = inOrder(o2); - inOrder2.verify(o2, times(1)).onNext(3); + InOrder inOrder2 = inOrder(subscriber2); + inOrder2.verify(subscriber2, times(1)).onNext(3); inOrder2.verifyNoMoreInteractions(); ts2.dispose(); @@ -302,8 +301,8 @@ public void testUnsubscriptionCase() { PublishProcessor<String> src = PublishProcessor.create(); for (int i = 0; i < 10; i++) { - final Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); String v = "" + i; System.out.printf("Turn: %d%n", i); src.firstElement().toFlowable() @@ -317,28 +316,27 @@ public Flowable<String> apply(String t1) { .subscribe(new DefaultSubscriber<String>() { @Override public void onNext(String t) { - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); src.onNext(v); - inOrder.verify(o).onNext(v + ", " + v); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext(v + ", " + v); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } - // FIXME RS subscribers are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { @@ -384,6 +382,7 @@ public void onComplete() { // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } + @Test public void testCurrentStateMethodsNormal() { PublishProcessor<Object> as = PublishProcessor.create(); @@ -419,6 +418,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { PublishProcessor<Object> as = PublishProcessor.create(); @@ -576,7 +576,7 @@ public void onComplete() { @Test public void terminateRace() throws Exception { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = pp.test(); @@ -588,7 +588,7 @@ public void run() { } }; - TestHelper.race(task, task, Schedulers.io()); + TestHelper.race(task, task); ts .awaitDone(5, TimeUnit.SECONDS) @@ -599,7 +599,7 @@ public void run() { @Test public void addRemoveRance() throws Exception { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final PublishProcessor<Integer> pp = PublishProcessor.create(); final TestSubscriber<Integer> ts = pp.test(); @@ -617,7 +617,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.io()); + TestHelper.race(r1, r2); } } @@ -677,4 +677,37 @@ public void run() { .awaitDone(5, TimeUnit.SECONDS) .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } + + @Test(timeout = 10000) + public void subscriberCancelOfferRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor<Integer> pp = PublishProcessor.create(); + + final TestSubscriber<Integer> ts = pp.test(1); + + Runnable r1 = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 2; i++) { + while (!pp.offer(i)) { } + } + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + + if (ts.valueCount() > 0) { + ts.assertValuesOnly(0); + } else { + ts.assertEmpty(); + } + } + } } diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java index 5dcc0fb4e4..0258488d52 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorBoundedConcurrencyTest.java @@ -39,13 +39,13 @@ public void run() { Flowable.unsafeCreate(new Publisher<Long>() { @Override - public void subscribe(Subscriber<? super Long> o) { + public void subscribe(Subscriber<? super Long> subscriber) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { - o.onNext(l); + subscriber.onNext(l); } System.out.println("********* Finished Source Data ***********"); - o.onComplete(); + subscriber.onComplete(); } }).subscribe(replay); } @@ -148,13 +148,13 @@ public void run() { Flowable.unsafeCreate(new Publisher<Long>() { @Override - public void subscribe(Subscriber<? super Long> o) { + public void subscribe(Subscriber<? super Long> subscriber) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { - o.onNext(l); + subscriber.onNext(l); } System.out.println("********* Finished Source Data ***********"); - o.onComplete(); + subscriber.onComplete(); } }).subscribe(replay); } @@ -227,10 +227,10 @@ public void run() { @Test(timeout = 10000) public void testSubscribeCompletionRaceCondition() { for (int i = 0; i < 50; i++) { - final ReplayProcessor<String> subject = ReplayProcessor.createUnbounded(); + final ReplayProcessor<String> processor = ReplayProcessor.createUnbounded(); final AtomicReference<String> value1 = new AtomicReference<String>(); - subject.subscribe(new Consumer<String>() { + processor.subscribe(new Consumer<String>() { @Override public void accept(String t1) { @@ -249,15 +249,15 @@ public void accept(String t1) { @Override public void run() { - subject.onNext("value"); - subject.onComplete(); + processor.onNext("value"); + processor.onComplete(); } }); - SubjectObserverThread t2 = new SubjectObserverThread(subject); - SubjectObserverThread t3 = new SubjectObserverThread(subject); - SubjectObserverThread t4 = new SubjectObserverThread(subject); - SubjectObserverThread t5 = new SubjectObserverThread(subject); + SubjectObserverThread t2 = new SubjectObserverThread(processor); + SubjectObserverThread t3 = new SubjectObserverThread(processor); + SubjectObserverThread t4 = new SubjectObserverThread(processor); + SubjectObserverThread t5 = new SubjectObserverThread(processor); t2.start(); t3.start(); @@ -284,6 +284,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -300,24 +301,25 @@ public void testRaceForTerminalState() { private static class SubjectObserverThread extends Thread { - private final ReplayProcessor<String> subject; + private final ReplayProcessor<String> processor; private final AtomicReference<String> value = new AtomicReference<String>(); - SubjectObserverThread(ReplayProcessor<String> subject) { - this.subject = subject; + SubjectObserverThread(ReplayProcessor<String> processor) { + this.processor = processor; } @Override public void run() { try { // a timeout exception will happen if we don't get a terminal state - String v = subject.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); + String v = processor.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); value.set(v); } catch (Exception e) { e.printStackTrace(); } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -403,6 +405,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -457,6 +460,7 @@ public void run() { t.join(); } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValueBounded() throws InterruptedException { final ReplayProcessor<Object> rs = ReplayProcessor.createWithSize(3); @@ -500,6 +504,7 @@ public void run() { t.join(); } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValueTimeBounded() throws InterruptedException { final ReplayProcessor<Object> rs = ReplayProcessor.createWithTime(1, TimeUnit.MILLISECONDS, Schedulers.computation()); diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java index ff9168339c..978c86ebe4 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorConcurrencyTest.java @@ -39,13 +39,13 @@ public void run() { Flowable.unsafeCreate(new Publisher<Long>() { @Override - public void subscribe(Subscriber<? super Long> o) { + public void subscribe(Subscriber<? super Long> subscriber) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { - o.onNext(l); + subscriber.onNext(l); } System.out.println("********* Finished Source Data ***********"); - o.onComplete(); + subscriber.onComplete(); } }).subscribe(replay); } @@ -148,13 +148,13 @@ public void run() { Flowable.unsafeCreate(new Publisher<Long>() { @Override - public void subscribe(Subscriber<? super Long> o) { + public void subscribe(Subscriber<? super Long> subscriber) { System.out.println("********* Start Source Data ***********"); for (long l = 1; l <= 10000; l++) { - o.onNext(l); + subscriber.onNext(l); } System.out.println("********* Finished Source Data ***********"); - o.onComplete(); + subscriber.onComplete(); } }).subscribe(replay); } @@ -227,10 +227,10 @@ public void run() { @Test(timeout = 10000) public void testSubscribeCompletionRaceCondition() { for (int i = 0; i < 50; i++) { - final ReplayProcessor<String> subject = ReplayProcessor.create(); + final ReplayProcessor<String> processor = ReplayProcessor.create(); final AtomicReference<String> value1 = new AtomicReference<String>(); - subject.subscribe(new Consumer<String>() { + processor.subscribe(new Consumer<String>() { @Override public void accept(String t1) { @@ -249,15 +249,15 @@ public void accept(String t1) { @Override public void run() { - subject.onNext("value"); - subject.onComplete(); + processor.onNext("value"); + processor.onComplete(); } }); - SubjectObserverThread t2 = new SubjectObserverThread(subject); - SubjectObserverThread t3 = new SubjectObserverThread(subject); - SubjectObserverThread t4 = new SubjectObserverThread(subject); - SubjectObserverThread t5 = new SubjectObserverThread(subject); + SubjectObserverThread t2 = new SubjectObserverThread(processor); + SubjectObserverThread t3 = new SubjectObserverThread(processor); + SubjectObserverThread t4 = new SubjectObserverThread(processor); + SubjectObserverThread t5 = new SubjectObserverThread(processor); t2.start(); t3.start(); @@ -284,6 +284,7 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test @@ -300,24 +301,25 @@ public void testRaceForTerminalState() { static class SubjectObserverThread extends Thread { - private final ReplayProcessor<String> subject; + private final ReplayProcessor<String> processor; private final AtomicReference<String> value = new AtomicReference<String>(); - SubjectObserverThread(ReplayProcessor<String> subject) { - this.subject = subject; + SubjectObserverThread(ReplayProcessor<String> processor) { + this.processor = processor; } @Override public void run() { try { // a timeout exception will happen if we don't get a terminal state - String v = subject.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); + String v = processor.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); value.set(v); } catch (Exception e) { e.printStackTrace(); } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -391,6 +393,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplayProcessor<Object> rs = ReplayProcessor.create(); diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java index 9d5c90f605..b59d20fb94 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java @@ -13,29 +13,26 @@ package io.reactivex.processors; -import io.reactivex.Flowable; -import io.reactivex.TestHelper; -import io.reactivex.disposables.Disposable; -import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; -import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.schedulers.TestScheduler; -import io.reactivex.subscribers.DefaultSubscriber; -import io.reactivex.subscribers.TestSubscriber; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import org.junit.*; +import org.mockito.*; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.processors.ReplayProcessor.*; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.*; public class ReplayProcessorTest extends FlowableProcessorTest<Object> { @@ -48,26 +45,26 @@ protected FlowableProcessor<Object> create() { @Test public void testCompleted() { - ReplayProcessor<String> subject = ReplayProcessor.create(); + ReplayProcessor<String> processor = ReplayProcessor.create(); - Subscriber<String> o1 = TestHelper.mockSubscriber(); - subject.subscribe(o1); + Subscriber<String> subscriber1 = TestHelper.mockSubscriber(); + processor.subscribe(subscriber1); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onComplete(); - subject.onNext("four"); - subject.onComplete(); - subject.onError(new Throwable()); + processor.onNext("four"); + processor.onComplete(); + processor.onError(new Throwable()); - assertCompletedSubscriber(o1); + assertCompletedSubscriber(subscriber1); // assert that subscribing a 2nd time gets the same data - Subscriber<String> o2 = TestHelper.mockSubscriber(); - subject.subscribe(o2); - assertCompletedSubscriber(o2); + Subscriber<String> subscriber2 = TestHelper.mockSubscriber(); + processor.subscribe(subscriber2); + assertCompletedSubscriber(subscriber2); } @Test @@ -141,126 +138,126 @@ public void testCompletedStopsEmittingData() { @Test public void testCompletedAfterError() { - ReplayProcessor<String> subject = ReplayProcessor.create(); + ReplayProcessor<String> processor = ReplayProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); - subject.onNext("one"); - subject.onError(testException); - subject.onNext("two"); - subject.onComplete(); - subject.onError(new RuntimeException()); + processor.onNext("one"); + processor.onError(testException); + processor.onNext("two"); + processor.onComplete(); + processor.onError(new RuntimeException()); - subject.subscribe(observer); - verify(observer).onSubscribe((Subscription)notNull()); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onError(testException); - verifyNoMoreInteractions(observer); + processor.subscribe(subscriber); + verify(subscriber).onSubscribe((Subscription)notNull()); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onError(testException); + verifyNoMoreInteractions(subscriber); } - private void assertCompletedSubscriber(Subscriber<String> observer) { - InOrder inOrder = inOrder(observer); + private void assertCompletedSubscriber(Subscriber<String> subscriber) { + InOrder inOrder = inOrder(subscriber); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, Mockito.never()).onError(any(Throwable.class)); - inOrder.verify(observer, times(1)).onComplete(); + inOrder.verify(subscriber, times(1)).onNext("one"); + inOrder.verify(subscriber, times(1)).onNext("two"); + inOrder.verify(subscriber, times(1)).onNext("three"); + inOrder.verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + inOrder.verify(subscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testError() { - ReplayProcessor<String> subject = ReplayProcessor.create(); + ReplayProcessor<String> processor = ReplayProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); - subject.onNext("three"); - subject.onError(testException); + processor.onNext("one"); + processor.onNext("two"); + processor.onNext("three"); + processor.onError(testException); - subject.onNext("four"); - subject.onError(new Throwable()); - subject.onComplete(); + processor.onNext("four"); + processor.onError(new Throwable()); + processor.onComplete(); - assertErrorSubscriber(observer); + assertErrorSubscriber(subscriber); - observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); - assertErrorSubscriber(observer); + subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); + assertErrorSubscriber(subscriber); } - private void assertErrorSubscriber(Subscriber<String> observer) { - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, times(1)).onError(testException); - verify(observer, Mockito.never()).onComplete(); + private void assertErrorSubscriber(Subscriber<String> subscriber) { + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, times(1)).onError(testException); + verify(subscriber, Mockito.never()).onComplete(); } @Test public void testSubscribeMidSequence() { - ReplayProcessor<String> subject = ReplayProcessor.create(); + ReplayProcessor<String> processor = ReplayProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - subject.subscribe(observer); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + processor.subscribe(subscriber); - subject.onNext("one"); - subject.onNext("two"); + processor.onNext("one"); + processor.onNext("two"); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); assertObservedUntilTwo(anotherSubscriber); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("three"); + processor.onComplete(); - assertCompletedSubscriber(observer); + assertCompletedSubscriber(subscriber); assertCompletedSubscriber(anotherSubscriber); } @Test public void testUnsubscribeFirstSubscriber() { - ReplayProcessor<String> subject = ReplayProcessor.create(); + ReplayProcessor<String> processor = ReplayProcessor.create(); - Subscriber<String> observer = TestHelper.mockSubscriber(); - TestSubscriber<String> ts = new TestSubscriber<String>(observer); - subject.subscribe(ts); + Subscriber<String> subscriber = TestHelper.mockSubscriber(); + TestSubscriber<String> ts = new TestSubscriber<String>(subscriber); + processor.subscribe(ts); - subject.onNext("one"); - subject.onNext("two"); + processor.onNext("one"); + processor.onNext("two"); ts.dispose(); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); Subscriber<String> anotherSubscriber = TestHelper.mockSubscriber(); - subject.subscribe(anotherSubscriber); + processor.subscribe(anotherSubscriber); assertObservedUntilTwo(anotherSubscriber); - subject.onNext("three"); - subject.onComplete(); + processor.onNext("three"); + processor.onComplete(); - assertObservedUntilTwo(observer); + assertObservedUntilTwo(subscriber); assertCompletedSubscriber(anotherSubscriber); } - private void assertObservedUntilTwo(Subscriber<String> observer) { - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, Mockito.never()).onNext("three"); - verify(observer, Mockito.never()).onError(any(Throwable.class)); - verify(observer, Mockito.never()).onComplete(); + private void assertObservedUntilTwo(Subscriber<String> subscriber) { + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, Mockito.never()).onNext("three"); + verify(subscriber, Mockito.never()).onError(any(Throwable.class)); + verify(subscriber, Mockito.never()).onComplete(); } @Test(timeout = 2000) public void testNewSubscriberDoesntBlockExisting() throws InterruptedException { final AtomicReference<String> lastValueForSubscriber1 = new AtomicReference<String>(); - Subscriber<String> observer1 = new DefaultSubscriber<String>() { + Subscriber<String> subscriber1 = new DefaultSubscriber<String>() { @Override public void onComplete() { @@ -284,7 +281,7 @@ public void onNext(String v) { final CountDownLatch oneReceived = new CountDownLatch(1); final CountDownLatch makeSlow = new CountDownLatch(1); final CountDownLatch completed = new CountDownLatch(1); - Subscriber<String> observer2 = new DefaultSubscriber<String>() { + Subscriber<String> subscriber2 = new DefaultSubscriber<String>() { @Override public void onComplete() { @@ -313,15 +310,15 @@ public void onNext(String v) { }; - ReplayProcessor<String> subject = ReplayProcessor.create(); - subject.subscribe(observer1); - subject.onNext("one"); + ReplayProcessor<String> processor = ReplayProcessor.create(); + processor.subscribe(subscriber1); + processor.onNext("one"); assertEquals("one", lastValueForSubscriber1.get()); - subject.onNext("two"); + processor.onNext("two"); assertEquals("two", lastValueForSubscriber1.get()); // use subscribeOn to make this async otherwise we deadlock as we are using CountDownLatches - subject.subscribeOn(Schedulers.newThread()).subscribe(observer2); + processor.subscribeOn(Schedulers.newThread()).subscribe(subscriber2); System.out.println("before waiting for one"); @@ -330,7 +327,7 @@ public void onNext(String v) { System.out.println("after waiting for one"); - subject.onNext("three"); + processor.onNext("three"); System.out.println("sent three"); @@ -339,9 +336,9 @@ public void onNext(String v) { System.out.println("about to send onComplete"); - subject.onComplete(); + processor.onComplete(); - System.out.println("completed subject"); + System.out.println("completed processor"); // release makeSlow.countDown(); @@ -353,15 +350,16 @@ public void onNext(String v) { assertEquals("three", lastValueForSubscriber2.get()); } + @Test public void testSubscriptionLeak() { ReplayProcessor<Object> replaySubject = ReplayProcessor.create(); - Disposable s = replaySubject.subscribe(); + Disposable connection = replaySubject.subscribe(); assertEquals(1, replaySubject.subscriberCount()); - s.dispose(); + connection.dispose(); assertEquals(0, replaySubject.subscriberCount()); } @@ -371,8 +369,8 @@ public void testUnsubscriptionCase() { ReplayProcessor<String> src = ReplayProcessor.create(); for (int i = 0; i < 10; i++) { - final Subscriber<Object> o = TestHelper.mockSubscriber(); - InOrder inOrder = inOrder(o); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); String v = "" + i; src.onNext(v); System.out.printf("Turn: %d%n", i); @@ -388,24 +386,25 @@ public Flowable<String> apply(String t1) { @Override public void onNext(String t) { System.out.println(t); - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); - inOrder.verify(o).onNext("0, 0"); - inOrder.verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + inOrder.verify(subscriber).onNext("0, 0"); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testTerminateOnce() { ReplayProcessor<Integer> source = ReplayProcessor.create(); @@ -413,30 +412,30 @@ public void testTerminateOnce() { source.onNext(2); source.onComplete(); - final Subscriber<Integer> o = TestHelper.mockSubscriber(); + final Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); source.subscribe(new DefaultSubscriber<Integer>() { @Override public void onNext(Integer t) { - o.onNext(t); + subscriber.onNext(t); } @Override public void onError(Throwable e) { - o.onError(e); + subscriber.onError(e); } @Override public void onComplete() { - o.onComplete(); + subscriber.onComplete(); } }); - verify(o).onNext(1); - verify(o).onNext(2); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -448,35 +447,36 @@ public void testReplay1AfterTermination() { source.onComplete(); for (int i = 0; i < 1; i++) { - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - source.subscribe(o); + source.subscribe(subscriber); - verify(o, never()).onNext(1); - verify(o).onNext(2); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } } + @Test public void testReplay1Directly() { ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(1); - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); source.onNext(1); source.onNext(2); - source.subscribe(o); + source.subscribe(subscriber); source.onNext(3); source.onComplete(); - verify(o, never()).onNext(1); - verify(o).onNext(2); - verify(o).onNext(3); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber).onNext(3); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -497,15 +497,15 @@ public void testReplayTimestampedAfterTermination() { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - source.subscribe(o); + source.subscribe(subscriber); - verify(o, never()).onNext(1); - verify(o, never()).onNext(2); - verify(o, never()).onNext(3); - verify(o).onComplete(); - verify(o, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(1); + verify(subscriber, never()).onNext(2); + verify(subscriber, never()).onNext(3); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); } @Test @@ -517,9 +517,9 @@ public void testReplayTimestampedDirectly() { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Subscriber<Integer> o = TestHelper.mockSubscriber(); + Subscriber<Integer> subscriber = TestHelper.mockSubscriber(); - source.subscribe(o); + source.subscribe(subscriber); source.onNext(2); @@ -533,17 +533,17 @@ public void testReplayTimestampedDirectly() { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - verify(o, never()).onError(any(Throwable.class)); - verify(o, never()).onNext(1); - verify(o).onNext(2); - verify(o).onNext(3); - verify(o).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, never()).onNext(1); + verify(subscriber).onNext(2); + verify(subscriber).onNext(3); + verify(subscriber).onComplete(); } // FIXME RS subscribers can't throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { -// ReplaySubject<String> ps = ReplaySubject.create(); +// ReplayProcessor<String> ps = ReplayProcessor.create(); // // ps.subscribe(); // TestSubscriber<String> ts = new TestSubscriber<String>(); @@ -565,7 +565,7 @@ public void testReplayTimestampedDirectly() { // */ // @Test // public void testOnErrorThrowsDoesntPreventDelivery2() { -// ReplaySubject<String> ps = ReplaySubject.create(); +// ReplayProcessor<String> ps = ReplayProcessor.create(); // // ps.subscribe(); // ps.subscribe(); @@ -621,6 +621,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { ReplayProcessor<Object> as = ReplayProcessor.create(); @@ -635,6 +636,7 @@ public void testCurrentStateMethodsError() { assertFalse(as.hasComplete()); assertTrue(as.getThrowable() instanceof TestException); } + @Test public void testSizeAndHasAnyValueUnbounded() { ReplayProcessor<Object> rs = ReplayProcessor.create(); @@ -657,6 +659,7 @@ public void testSizeAndHasAnyValueUnbounded() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnbounded() { ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -702,6 +705,7 @@ public void testSizeAndHasAnyValueUnboundedError() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedError() { ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -734,6 +738,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyError() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyError() { ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -753,6 +758,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyCompleted() { ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -805,6 +811,7 @@ public void testSizeAndHasAnyValueTimeBounded() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testGetValues() { ReplayProcessor<Object> rs = ReplayProcessor.create(); @@ -819,6 +826,7 @@ public void testGetValues() { assertArrayEquals(expected, rs.getValues()); } + @Test public void testGetValuesUnbounded() { ReplayProcessor<Object> rs = ReplayProcessor.createUnbounded(); @@ -851,7 +859,6 @@ public void testBackpressureHonored() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -880,7 +887,6 @@ public void testBackpressureHonoredSizeBound() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -909,7 +915,6 @@ public void testBackpressureHonoredTimeBound() { ts.assertNotComplete(); ts.assertNoErrors(); - ts.request(1); ts.assertValues(1, 2); ts.assertNotComplete(); @@ -1045,7 +1050,7 @@ public void peekStateTimeAndSizeValueExpired() { scheduler.advanceTimeBy(2, TimeUnit.DAYS); - assertEquals(null, rp.getValue()); + assertNull(rp.getValue()); assertEquals(0, rp.getValues().length); assertNull(rp.getValues(new Integer[2])[0]); } @@ -1064,7 +1069,7 @@ public void capacityHint() { @Test public void subscribeCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final ReplayProcessor<Integer> rp = ReplayProcessor.create(); @@ -1083,7 +1088,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -1101,7 +1106,7 @@ public void subscribeAfterDone() { @Test public void subscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplayProcessor<Integer> rp = ReplayProcessor.create(); Runnable r1 = new Runnable() { @@ -1111,7 +1116,7 @@ public void run() { } }; - TestHelper.race(r1, r1, Schedulers.single()); + TestHelper.race(r1, r1); } } @@ -1130,7 +1135,7 @@ public void cancelUpfront() { @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplayProcessor<Integer> rp = ReplayProcessor.create(); final TestSubscriber<Integer> ts1 = rp.test(); @@ -1150,7 +1155,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertFalse(rp.hasSubscribers()); } @@ -1186,7 +1191,7 @@ public void sizeAndTimeBoundReplayError() { @Test public void replayRequestRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.DAYS, Schedulers.single(), 2); final TestSubscriber<Integer> ts = rp.test(0L); @@ -1205,7 +1210,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -1318,4 +1323,506 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void unboundedRequestCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ReplayProcessor<Integer> source = ReplayProcessor.create(); + + final TestSubscriber<Integer> ts = source.test(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(); + } + } + + @Test + public void sizeRequestCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(10); + + final TestSubscriber<Integer> ts = source.test(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(); + } + } + + @Test + public void timedRequestCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(2, TimeUnit.HOURS, Schedulers.single()); + + final TestSubscriber<Integer> ts = source.test(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(); + } + } + + @Test + public void timeAndSizeRequestCompleteRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTimeAndSize(2, TimeUnit.HOURS, Schedulers.single(), 100); + + final TestSubscriber<Integer> ts = source.test(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + source.onComplete(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.request(1); + } + }; + + TestHelper.race(r1, r2); + + ts.assertResult(); + } + } + + @Test + public void unboundedZeroRequestComplete() { + final ReplayProcessor<Integer> source = ReplayProcessor.create(); + + source.onComplete(); + + source.test(0).assertResult(); + } + + @Test + public void unboundedZeroRequestError() { + final ReplayProcessor<Integer> source = ReplayProcessor.create(); + + source.onError(new TestException()); + + source.test(0).assertFailure(TestException.class); + } + + @Test + public void sizeBoundZeroRequestComplete() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(16); + + source.onComplete(); + + source.test(0).assertResult(); + } + + @Test + public void sizeBoundZeroRequestError() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(16); + + source.onError(new TestException()); + + source.test(0).assertFailure(TestException.class); + } + + @Test + public void timeBoundZeroRequestComplete() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MINUTES, Schedulers.single()); + + source.onComplete(); + + source.test(0).assertResult(); + } + + @Test + public void timeBoundZeroRequestError() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MINUTES, Schedulers.single()); + + source.onError(new TestException()); + + source.test(0).assertFailure(TestException.class); + } + + @Test + public void timeAndSizeBoundZeroRequestComplete() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.MINUTES, Schedulers.single(), 16); + + source.onComplete(); + + source.test(0).assertResult(); + } + + @Test + public void timeAndSizeBoundZeroRequestError() { + final ReplayProcessor<Integer> source = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.MINUTES, Schedulers.single(), 16); + + source.onError(new TestException()); + + source.test(0).assertFailure(TestException.class); + } + + TestSubscriber<Integer> take1AndCancel() { + return new TestSubscriber<Integer>(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + } + + @Test + public void unboundedCancelAfterOne() { + ReplayProcessor<Integer> source = ReplayProcessor.create(); + source.onNext(1); + + source.subscribeWith(take1AndCancel()) + .assertResult(1); + } + + @Test + public void sizeBoundCancelAfterOne() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(16); + source.onNext(1); + + source.subscribeWith(take1AndCancel()) + .assertResult(1); + } + + @Test + public void timeBoundCancelAfterOne() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MINUTES, Schedulers.single()); + source.onNext(1); + + source.subscribeWith(take1AndCancel()) + .assertResult(1); + } + + @Test + public void timeAndSizeBoundCancelAfterOne() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.MINUTES, Schedulers.single(), 16); + source.onNext(1); + + source.subscribeWith(take1AndCancel()) + .assertResult(1); + } + + @Test + public void noHeadRetentionCompleteSize() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(1); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + SizeBoundReplayBuffer<Integer> buf = (SizeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionErrorSize() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(1); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + SizeBoundReplayBuffer<Integer> buf = (SizeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void unboundedCleanupBufferNoOp() { + ReplayProcessor<Integer> source = ReplayProcessor.create(1); + + source.onNext(1); + source.onNext(2); + + source.cleanupBuffer(); + + source.test().assertValuesOnly(1, 2); + } + + @Test + public void noHeadRetentionSize() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithSize(1); + + source.onNext(1); + source.onNext(2); + + SizeBoundReplayBuffer<Integer> buf = (SizeBoundReplayBuffer<Integer>)source.buffer; + + assertNotNull(buf.head.value); + + source.cleanupBuffer(); + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionCompleteTime() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MINUTES, Schedulers.computation()); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + SizeAndTimeBoundReplayBuffer<Integer> buf = (SizeAndTimeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionErrorTime() { + ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MINUTES, Schedulers.computation()); + + source.onNext(1); + source.onNext(2); + source.onError(new TestException()); + + SizeAndTimeBoundReplayBuffer<Integer> buf = (SizeAndTimeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionTime() { + TestScheduler sch = new TestScheduler(); + + ReplayProcessor<Integer> source = ReplayProcessor.createWithTime(1, TimeUnit.MILLISECONDS, sch); + + source.onNext(1); + + sch.advanceTimeBy(2, TimeUnit.MILLISECONDS); + + source.onNext(2); + + SizeAndTimeBoundReplayBuffer<Integer> buf = (SizeAndTimeBoundReplayBuffer<Integer>)source.buffer; + + assertNotNull(buf.head.value); + + source.cleanupBuffer(); + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void invalidRequest() { + TestHelper.assertBadRequestReported(ReplayProcessor.create()); + } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + final ReplayProcessor<byte[]> rp = ReplayProcessor.createWithSize(1); + + Flowable<byte[]> source = rp.take(1) + .concatMap(new Function<byte[], Publisher<byte[]>>() { + @Override + public Publisher<byte[]> apply(byte[] v) throws Exception { + return rp; + } + }) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer<byte[]>() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + for (int i = 0; i < 200; i++) { + rp.onNext(new byte[1024 * 1024]); + } + rp.onComplete(); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange() { + ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber<Integer> ts = rp.test(); + + rp.onNext(1); + rp.cleanupBuffer(); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange2() { + ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber<Integer> ts = rp.test(); + + rp.onNext(1); + rp.cleanupBuffer(); + rp.onNext(2); + rp.cleanupBuffer(); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange3() { + ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber<Integer> ts = rp.test(); + + rp.onNext(1); + rp.onNext(2); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange4() { + ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 10); + + TestSubscriber<Integer> ts = rp.test(); + + rp.onNext(1); + rp.onNext(2); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeRemoveCorrectNumberOfOld() { + TestScheduler scheduler = new TestScheduler(); + ReplayProcessor<Integer> rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, scheduler, 2); + + rp.onNext(1); + rp.onNext(2); + rp.onNext(3); + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + rp.onNext(4); + rp.onNext(5); + + rp.test().assertValuesOnly(4, 5); + } } diff --git a/src/test/java/io/reactivex/processors/SerializedProcessorTest.java b/src/test/java/io/reactivex/processors/SerializedProcessorTest.java index ba981bde07..9d5b51c152 100644 --- a/src/test/java/io/reactivex/processors/SerializedProcessorTest.java +++ b/src/test/java/io/reactivex/processors/SerializedProcessorTest.java @@ -23,22 +23,22 @@ import io.reactivex.exceptions.TestException; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class SerializedProcessorTest { @Test public void testBasic() { - SerializedProcessor<String> subject = new SerializedProcessor<String>(PublishProcessor.<String> create()); + SerializedProcessor<String> processor = new SerializedProcessor<String>(PublishProcessor.<String> create()); TestSubscriber<String> ts = new TestSubscriber<String>(); - subject.subscribe(ts); - subject.onNext("hello"); - subject.onComplete(); + processor.subscribe(ts); + processor.onNext("hello"); + processor.onComplete(); ts.awaitTerminalEvent(); ts.assertValue("hello"); } + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueRelay() { AsyncProcessor<Integer> async = AsyncProcessor.create(); @@ -57,6 +57,8 @@ public void testAsyncSubjectValueRelay() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueEmpty() { AsyncProcessor<Integer> async = AsyncProcessor.create(); @@ -74,6 +76,8 @@ public void testAsyncSubjectValueEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueError() { AsyncProcessor<Integer> async = AsyncProcessor.create(); @@ -92,6 +96,7 @@ public void testAsyncSubjectValueError() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testPublishSubjectValueRelay() { PublishProcessor<Integer> async = PublishProcessor.create(); @@ -116,6 +121,7 @@ public void testPublishSubjectValueEmpty() { assertFalse(serial.hasThrowable()); assertNull(serial.getThrowable()); } + @Test public void testPublishSubjectValueError() { PublishProcessor<Integer> async = PublishProcessor.create(); @@ -129,6 +135,7 @@ public void testPublishSubjectValueError() { assertSame(te, serial.getThrowable()); } + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectValueRelay() { BehaviorProcessor<Integer> async = BehaviorProcessor.create(); @@ -147,6 +154,8 @@ public void testBehaviorSubjectValueRelay() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectValueRelayIncomplete() { BehaviorProcessor<Integer> async = BehaviorProcessor.create(); @@ -164,6 +173,8 @@ public void testBehaviorSubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectIncompleteEmpty() { BehaviorProcessor<Integer> async = BehaviorProcessor.create(); @@ -180,6 +191,8 @@ public void testBehaviorSubjectIncompleteEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectEmpty() { BehaviorProcessor<Integer> async = BehaviorProcessor.create(); @@ -197,6 +210,8 @@ public void testBehaviorSubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectError() { BehaviorProcessor<Integer> async = BehaviorProcessor.create(); @@ -234,6 +249,7 @@ public void testReplaySubjectValueRelay() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayIncomplete() { ReplayProcessor<Integer> async = ReplayProcessor.create(); @@ -251,6 +267,7 @@ public void testReplaySubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBounded() { ReplayProcessor<Integer> async = ReplayProcessor.createWithSize(1); @@ -270,6 +287,7 @@ public void testReplaySubjectValueRelayBounded() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedIncomplete() { ReplayProcessor<Integer> async = ReplayProcessor.createWithSize(1); @@ -288,6 +306,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { ReplayProcessor<Integer> async = ReplayProcessor.createWithSize(1); @@ -304,6 +323,7 @@ public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayEmptyIncomplete() { ReplayProcessor<Integer> async = ReplayProcessor.create(); @@ -338,6 +358,7 @@ public void testReplaySubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectError() { ReplayProcessor<Integer> async = ReplayProcessor.create(); @@ -374,6 +395,7 @@ public void testReplaySubjectBoundedEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectBoundedError() { ReplayProcessor<Integer> async = ReplayProcessor.createWithSize(1); @@ -432,7 +454,7 @@ public void normal() { @Test public void onNextOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -451,7 +473,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertSubscribed().assertNoErrors().assertNotComplete() .assertValueSet(Arrays.asList(1, 2)); @@ -460,7 +482,7 @@ public void run() { @Test public void onNextOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -481,7 +503,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertError(ex).assertNotComplete(); @@ -493,7 +515,7 @@ public void run() { @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -512,7 +534,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertComplete().assertNoErrors(); @@ -524,7 +546,7 @@ public void run() { @Test public void onNextOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -545,7 +567,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertValue(1).assertNotComplete().assertNoErrors(); } @@ -553,7 +575,7 @@ public void run() { @Test public void onCompleteOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -574,7 +596,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertResult(); } @@ -582,7 +604,7 @@ public void run() { @Test public void onCompleteOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -601,7 +623,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertResult(); } @@ -609,7 +631,7 @@ public void run() { @Test public void onErrorOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -632,7 +654,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertFailure(TestException.class); @@ -645,7 +667,7 @@ public void run() { @Test public void onSubscribeOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final FlowableProcessor<Integer> s = PublishProcessor.<Integer>create().toSerialized(); TestSubscriber<Integer> ts = s.test(); @@ -667,7 +689,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.assertEmpty(); } diff --git a/src/test/java/io/reactivex/processors/UnicastProcessorTest.java b/src/test/java/io/reactivex/processors/UnicastProcessorTest.java index fbec729694..7b96e03b53 100644 --- a/src/test/java/io/reactivex/processors/UnicastProcessorTest.java +++ b/src/test/java/io/reactivex/processors/UnicastProcessorTest.java @@ -13,24 +13,26 @@ package io.reactivex.processors; -import io.reactivex.Observable; -import io.reactivex.TestHelper; -import io.reactivex.disposables.Disposable; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.QueueSubscription; -import io.reactivex.internal.subscriptions.BooleanSubscription; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subscribers.SubscriberFusion; -import io.reactivex.subscribers.TestSubscriber; -import org.junit.Test; +import static org.junit.Assert.*; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import static org.junit.Assert.*; +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.QueueFuseable; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.*; -public class UnicastProcessorTest extends DelayedFlowableProcessorTest<Object> { +public class UnicastProcessorTest extends FlowableProcessorTest<Object> { @Override protected FlowableProcessor<Object> create() { @@ -41,13 +43,13 @@ protected FlowableProcessor<Object> create() { public void fusionLive() { UnicastProcessor<Integer> ap = UnicastProcessor.create(); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); ap.subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)); + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)); ts.assertNoValues().assertNoErrors().assertNotComplete(); @@ -66,13 +68,13 @@ public void fusionOfflie() { ap.onNext(1); ap.onComplete(); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); ap.subscribe(ts); ts .assertOf(SubscriberFusion.<Integer>assertFuseable()) - .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1); } @@ -97,7 +99,7 @@ public void failFastFusionOffline() { ap.onNext(1); ap.onError(new RuntimeException()); - TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueFuseable.ANY); ap.subscribe(ts); ts @@ -112,7 +114,7 @@ public void threeArgsFactory() { public void run() { } }; - UnicastProcessor<Integer> ap = UnicastProcessor.create(16, noop,false); + UnicastProcessor<Integer> ap = UnicastProcessor.create(16, noop, false); ap.onNext(1); ap.onError(new RuntimeException()); @@ -134,9 +136,9 @@ public void onTerminateCalledWhenOnError() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onError(new RuntimeException("some error")); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -149,9 +151,9 @@ public void onTerminateCalledWhenOnComplete() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onComplete(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -166,9 +168,9 @@ public void onTerminateCalledWhenCanceled() { final Disposable subscribe = us.subscribe(); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); subscribe.dispose(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test(expected = NullPointerException.class) @@ -188,7 +190,7 @@ public void zeroCapacityHint() { @Test public void completeCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final int[] calls = { 0 }; final UnicastProcessor<Object> up = UnicastProcessor.create(100, new Runnable() { @Override @@ -213,7 +215,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertEquals(1, calls[0]); } @@ -265,11 +267,11 @@ public void onErrorStatePeeking() { public void rejectSyncFusion() { UnicastProcessor<Object> p = UnicastProcessor.create(); - TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueSubscription.SYNC); + TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueFuseable.SYNC); p.subscribe(ts); - SubscriberFusion.assertFusion(ts, QueueSubscription.NONE); + SubscriberFusion.assertFusion(ts, QueueFuseable.NONE); } @Test @@ -296,10 +298,10 @@ public void multiSubscriber() { @Test public void fusedDrainCancel() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final UnicastProcessor<Object> p = UnicastProcessor.create(); - final TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueSubscription.ANY); + final TestSubscriber<Object> ts = SubscriberFusion.newTest(QueueFuseable.ANY); p.subscribe(ts); @@ -317,7 +319,160 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); + } + } + + @Test + public void subscribeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final UnicastProcessor<Integer> us = UnicastProcessor.create(); + + final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); + final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + us.subscribe(ts1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + us.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2); + + if (ts1.errorCount() == 0) { + ts2.assertFailure(IllegalStateException.class); + } else + if (ts2.errorCount() == 0) { + ts1.assertFailure(IllegalStateException.class); + } else { + fail("Neither TestObserver failed"); + } + } + } + + @Test + public void hasObservers() { + UnicastProcessor<Integer> us = UnicastProcessor.create(); + + assertFalse(us.hasSubscribers()); + + TestSubscriber<Integer> ts = us.test(); + + assertTrue(us.hasSubscribers()); + + ts.cancel(); + + assertFalse(us.hasSubscribers()); + } + + @Test + public void drainFusedFailFast() { + UnicastProcessor<Integer> us = UnicastProcessor.create(false); + + TestSubscriber<Integer> ts = us.to(SubscriberFusion.<Integer>test(1, QueueFuseable.ANY, false)); + + us.done = true; + us.drainFused(ts); + + ts.assertResult(); + } + + @Test + public void drainFusedFailFastEmpty() { + UnicastProcessor<Integer> us = UnicastProcessor.create(false); + + TestSubscriber<Integer> ts = us.to(SubscriberFusion.<Integer>test(1, QueueFuseable.ANY, false)); + + us.drainFused(ts); + + ts.assertEmpty(); + } + + @Test + public void checkTerminatedFailFastEmpty() { + UnicastProcessor<Integer> us = UnicastProcessor.create(false); + + TestSubscriber<Integer> ts = us.to(SubscriberFusion.<Integer>test(1, QueueFuseable.ANY, false)); + + us.checkTerminated(true, true, false, ts, us.queue); + + ts.assertEmpty(); + } + + @Test + public void alreadyCancelled() { + UnicastProcessor<Integer> us = UnicastProcessor.create(false); + + us.test().cancel(); + + BooleanSubscription bs = new BooleanSubscription(); + us.onSubscribe(bs); + + assertTrue(bs.isCancelled()); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + us.onError(new TestException()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void unicastSubscriptionBadRequest() { + UnicastProcessor<Integer> us = UnicastProcessor.create(false); + + UnicastProcessor<Integer>.UnicastQueueSubscription usc = (UnicastProcessor<Integer>.UnicastQueueSubscription)us.wip; + + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + usc.request(-1); + TestHelper.assertError(errors, 0, IllegalArgumentException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final UnicastProcessor<Integer> us = UnicastProcessor.create(); + + TestObserver<Integer> to = us + .observeOn(Schedulers.io()) + .map(Functions.<Integer>identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; us.hasSubscribers(); i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } } } } diff --git a/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java b/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java index f34ed7b604..af489f5650 100644 --- a/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java @@ -36,6 +36,7 @@ public abstract class AbstractSchedulerConcurrencyTests extends AbstractSchedulerTests { /** + * Make sure canceling through {@code subscribeOn} works. * Bug report: https://github.com/ReactiveX/RxJava/issues/431 * @throws InterruptedException if the test is interrupted */ @@ -290,11 +291,11 @@ public void testRecursionAndOuterUnsubscribe() throws InterruptedException { try { Flowable<Integer> obs = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> observer) { + public void subscribe(final Subscriber<? super Integer> subscriber) { inner.schedule(new Runnable() { @Override public void run() { - observer.onNext(42); + subscriber.onNext(42); latch.countDown(); // this will recursively schedule this task for execution again @@ -302,12 +303,12 @@ public void run() { } }); - observer.onSubscribe(new Subscription() { + subscriber.onSubscribe(new Subscription() { @Override public void cancel() { inner.dispose(); - observer.onComplete(); + subscriber.onComplete(); completionLatch.countDown(); } @@ -368,9 +369,9 @@ public final void testSubscribeWithScheduler() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); - Flowable<Integer> o1 = Flowable.<Integer> just(1, 2, 3, 4, 5); + Flowable<Integer> f1 = Flowable.<Integer> just(1, 2, 3, 4, 5); - o1.subscribe(new Consumer<Integer>() { + f1.subscribe(new Consumer<Integer>() { @Override public void accept(Integer t) { @@ -393,7 +394,7 @@ public void accept(Integer t) { final CountDownLatch latch = new CountDownLatch(5); final CountDownLatch first = new CountDownLatch(1); - o1.subscribeOn(scheduler).subscribe(new Consumer<Integer>() { + f1.subscribeOn(scheduler).subscribe(new Consumer<Integer>() { @Override public void accept(Integer t) { @@ -404,7 +405,7 @@ public void accept(Integer t) { throw new RuntimeException("The latch should have released if we are async.", e); } - assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + assertNotEquals(Thread.currentThread().getName(), currentThreadName); System.out.println("Thread: " + Thread.currentThread().getName()); System.out.println("t: " + t); count.incrementAndGet(); diff --git a/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java b/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java index 74c2ecdb0d..0c9755fd60 100644 --- a/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java +++ b/src/test/java/io/reactivex/schedulers/AbstractSchedulerTests.java @@ -20,6 +20,8 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; import org.junit.Test; import org.mockito.InOrder; import org.mockito.invocation.InvocationOnMock; @@ -41,6 +43,7 @@ public abstract class AbstractSchedulerTests { /** * The scheduler to test. + * * @return the Scheduler instance */ protected abstract Scheduler getScheduler(); @@ -326,11 +329,11 @@ public void run() { public final void testRecursiveSchedulerInObservable() { Flowable<Integer> obs = Flowable.unsafeCreate(new Publisher<Integer>() { @Override - public void subscribe(final Subscriber<? super Integer> observer) { + public void subscribe(final Subscriber<? super Integer> subscriber) { final Scheduler.Worker inner = getScheduler().createWorker(); AsyncSubscription as = new AsyncSubscription(); - observer.onSubscribe(as); + subscriber.onSubscribe(as); as.setResource(inner); inner.schedule(new Runnable() { @@ -340,14 +343,14 @@ public void subscribe(final Subscriber<? super Integer> observer) { public void run() { if (i > 42) { try { - observer.onComplete(); + subscriber.onComplete(); } finally { inner.dispose(); } return; } - observer.onNext(i++); + subscriber.onNext(i++); inner.schedule(this); } @@ -372,18 +375,18 @@ public void accept(Integer v) { public final void testConcurrentOnNextFailsValidation() throws InterruptedException { final int count = 10; final CountDownLatch latch = new CountDownLatch(count); - Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { + Flowable<String> f = Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); for (int i = 0; i < count; i++) { final int v = i; new Thread(new Runnable() { @Override public void run() { - observer.onNext("v: " + v); + subscriber.onNext("v: " + v); latch.countDown(); } @@ -394,7 +397,7 @@ public void run() { ConcurrentObserverValidator<String> observer = new ConcurrentObserverValidator<String>(); // this should call onNext concurrently - o.subscribe(observer); + f.subscribe(observer); if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { fail("timed out"); @@ -409,11 +412,11 @@ public void run() { public final void testObserveOn() throws InterruptedException { final Scheduler scheduler = getScheduler(); - Flowable<String> o = Flowable.fromArray("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); + Flowable<String> f = Flowable.fromArray("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); ConcurrentObserverValidator<String> observer = new ConcurrentObserverValidator<String>(); - o.observeOn(scheduler).subscribe(observer); + f.observeOn(scheduler).subscribe(observer); if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { fail("timed out"); @@ -429,7 +432,7 @@ public final void testObserveOn() throws InterruptedException { public final void testSubscribeOnNestedConcurrency() throws InterruptedException { final Scheduler scheduler = getScheduler(); - Flowable<String> o = Flowable.fromArray("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") + Flowable<String> f = Flowable.fromArray("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") .flatMap(new Function<String, Flowable<String>>() { @Override @@ -437,10 +440,10 @@ public Flowable<String> apply(final String v) { return Flowable.unsafeCreate(new Publisher<String>() { @Override - public void subscribe(Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); - observer.onNext("value_after_map-" + v); - observer.onComplete(); + public void subscribe(Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext("value_after_map-" + v); + subscriber.onComplete(); } }).subscribeOn(scheduler); } @@ -448,7 +451,7 @@ public void subscribe(Subscriber<? super String> observer) { ConcurrentObserverValidator<String> observer = new ConcurrentObserverValidator<String>(); - o.subscribe(observer); + f.subscribe(observer); if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { fail("timed out"); @@ -576,6 +579,7 @@ public void schedulePeriodicallyDirectZeroPeriod() throws Exception { try { sd.replace(s.schedulePeriodicallyDirect(new Runnable() { int count; + @Override public void run() { if (++count == 10) { @@ -610,6 +614,7 @@ public void schedulePeriodicallyZeroPeriod() throws Exception { try { sd.replace(w.schedulePeriodically(new Runnable() { int count; + @Override public void run() { if (++count == 10) { @@ -626,4 +631,144 @@ public void run() { } } } + + private void assertRunnableDecorated(Runnable scheduleCall) throws InterruptedException { + try { + final CountDownLatch decoratedCalled = new CountDownLatch(1); + + RxJavaPlugins.setScheduleHandler(new Function<Runnable, Runnable>() { + @Override + public Runnable apply(final Runnable actual) throws Exception { + return new Runnable() { + @Override + public void run() { + decoratedCalled.countDown(); + actual.run(); + } + }; + } + }); + + scheduleCall.run(); + + assertTrue(decoratedCalled.await(5, TimeUnit.SECONDS)); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test(timeout = 6000) + public void scheduleDirectDecoratesRunnable() throws InterruptedException { + assertRunnableDecorated(new Runnable() { + @Override + public void run() { + getScheduler().scheduleDirect(Functions.EMPTY_RUNNABLE); + } + }); + } + + @Test(timeout = 6000) + public void scheduleDirectWithDelayDecoratesRunnable() throws InterruptedException { + assertRunnableDecorated(new Runnable() { + @Override + public void run() { + getScheduler().scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MILLISECONDS); + } + }); + } + + @Test(timeout = 6000) + public void schedulePeriodicallyDirectDecoratesRunnable() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + if (scheduler instanceof TrampolineScheduler) { + // Can't properly stop a trampolined periodic task. + return; + } + + final AtomicReference<Disposable> disposable = new AtomicReference<Disposable>(); + + try { + assertRunnableDecorated(new Runnable() { + @Override + public void run() { + disposable.set(scheduler.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 1, 10000, TimeUnit.MILLISECONDS)); + } + }); + } finally { + disposable.get().dispose(); + } + } + + @Test(timeout = 5000) + public void unwrapDefaultPeriodicTask() throws InterruptedException { + Scheduler s = getScheduler(); + if (s instanceof TrampolineScheduler) { + // TrampolineScheduler always return EmptyDisposable + return; + } + + final CountDownLatch cdl = new CountDownLatch(1); + Runnable countDownRunnable = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + Disposable disposable = s.schedulePeriodicallyDirect(countDownRunnable, 100, 100, TimeUnit.MILLISECONDS); + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) disposable; + + assertSame(countDownRunnable, wrapper.getWrappedRunnable()); + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + disposable.dispose(); + } + + @Test + public void unwrapScheduleDirectTask() { + Scheduler scheduler = getScheduler(); + if (scheduler instanceof TrampolineScheduler) { + // TrampolineScheduler always return EmptyDisposable + return; + } + final CountDownLatch cdl = new CountDownLatch(1); + Runnable countDownRunnable = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + Disposable disposable = scheduler.scheduleDirect(countDownRunnable, 100, TimeUnit.MILLISECONDS); + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) disposable; + assertSame(countDownRunnable, wrapper.getWrappedRunnable()); + disposable.dispose(); + } + + @Test + public void scheduleDirectNullRunnable() { + try { + getScheduler().scheduleDirect(null); + fail(); + } catch (NullPointerException npe) { + assertEquals("run is null", npe.getMessage()); + } + } + + @Test + public void scheduleDirectWithDelayNullRunnable() { + try { + getScheduler().scheduleDirect(null, 10, TimeUnit.MILLISECONDS); + fail(); + } catch (NullPointerException npe) { + assertEquals("run is null", npe.getMessage()); + } + } + + @Test + public void schedulePeriodicallyDirectNullRunnable() { + try { + getScheduler().schedulePeriodicallyDirect(null, 5, 10, TimeUnit.MILLISECONDS); + fail(); + } catch (NullPointerException npe) { + assertEquals("run is null", npe.getMessage()); + } + } } diff --git a/src/test/java/io/reactivex/schedulers/CachedThreadSchedulerTest.java b/src/test/java/io/reactivex/schedulers/CachedThreadSchedulerTest.java index 61b14fac21..df6ba44670 100644 --- a/src/test/java/io/reactivex/schedulers/CachedThreadSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/CachedThreadSchedulerTest.java @@ -38,9 +38,9 @@ protected Scheduler getScheduler() { @Test public final void testIOScheduler() { - Flowable<Integer> o1 = Flowable.just(1, 2, 3, 4, 5); - Flowable<Integer> o2 = Flowable.just(6, 7, 8, 9, 10); - Flowable<String> o = Flowable.merge(o1, o2).map(new Function<Integer, String>() { + Flowable<Integer> f1 = Flowable.just(1, 2, 3, 4, 5); + Flowable<Integer> f2 = Flowable.just(6, 7, 8, 9, 10); + Flowable<String> f = Flowable.merge(f1, f2).map(new Function<Integer, String>() { @Override public String apply(Integer t) { @@ -49,7 +49,7 @@ public String apply(Integer t) { } }); - o.subscribeOn(Schedulers.io()).blockingForEach(new Consumer<String>() { + f.subscribeOn(Schedulers.io()).blockingForEach(new Consumer<String>() { @Override public void accept(String t) { diff --git a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java index 67912db80d..86ae8ac54e 100644 --- a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java +++ b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java @@ -91,9 +91,9 @@ public void run() { @Test public final void testComputationThreadPool1() { - Flowable<Integer> o1 = Flowable.<Integer> just(1, 2, 3, 4, 5); - Flowable<Integer> o2 = Flowable.<Integer> just(6, 7, 8, 9, 10); - Flowable<String> o = Flowable.<Integer> merge(o1, o2).map(new Function<Integer, String>() { + Flowable<Integer> f1 = Flowable.<Integer> just(1, 2, 3, 4, 5); + Flowable<Integer> f2 = Flowable.<Integer> just(6, 7, 8, 9, 10); + Flowable<String> f = Flowable.<Integer> merge(f1, f2).map(new Function<Integer, String>() { @Override public String apply(Integer t) { @@ -102,7 +102,7 @@ public String apply(Integer t) { } }); - o.subscribeOn(Schedulers.computation()).blockingForEach(new Consumer<String>() { + f.subscribeOn(Schedulers.computation()).blockingForEach(new Consumer<String>() { @Override public void accept(String t) { @@ -111,25 +111,24 @@ public void accept(String t) { }); } - @Test public final void testMergeWithExecutorScheduler() { final String currentThreadName = Thread.currentThread().getName(); - Flowable<Integer> o1 = Flowable.<Integer> just(1, 2, 3, 4, 5); - Flowable<Integer> o2 = Flowable.<Integer> just(6, 7, 8, 9, 10); - Flowable<String> o = Flowable.<Integer> merge(o1, o2).subscribeOn(Schedulers.computation()).map(new Function<Integer, String>() { + Flowable<Integer> f1 = Flowable.<Integer> just(1, 2, 3, 4, 5); + Flowable<Integer> f2 = Flowable.<Integer> just(6, 7, 8, 9, 10); + Flowable<String> f = Flowable.<Integer> merge(f1, f2).subscribeOn(Schedulers.computation()).map(new Function<Integer, String>() { @Override public String apply(Integer t) { - assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + assertNotEquals(Thread.currentThread().getName(), currentThreadName); assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); } }); - o.blockingForEach(new Consumer<String>() { + f.blockingForEach(new Consumer<String>() { @Override public void accept(String t) { diff --git a/src/test/java/io/reactivex/schedulers/ExecutorSchedulerInterruptibleTest.java b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerInterruptibleTest.java new file mode 100644 index 0000000000..bccca7524e --- /dev/null +++ b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerInterruptibleTest.java @@ -0,0 +1,664 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.schedulers; + +import static org.junit.Assert.*; + +import java.lang.management.*; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.schedulers.*; +import io.reactivex.plugins.RxJavaPlugins; + +public class ExecutorSchedulerInterruptibleTest extends AbstractSchedulerConcurrencyTests { + + static final Executor executor = Executors.newFixedThreadPool(2, new RxThreadFactory("TestCustomPool")); + + @Override + protected Scheduler getScheduler() { + return Schedulers.from(executor, true); + } + + @Test + @Ignore("Unhandled errors are no longer thrown") + public final void testUnhandledErrorIsDeliveredToThreadHandler() throws InterruptedException { + SchedulerTestHelper.testUnhandledErrorIsDeliveredToThreadHandler(getScheduler()); + } + + @Test + public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException { + SchedulerTestHelper.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler()); + } + + public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) throws InterruptedException { + System.out.println("Wait before GC"); + Thread.sleep(1000); + + System.out.println("GC"); + System.gc(); + + Thread.sleep(1000); + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + int n = 100 * 1000; + if (periodic) { + final CountDownLatch cdl = new CountDownLatch(n); + final Runnable action = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + for (int i = 0; i < n; i++) { + if (i % 50000 == 0) { + System.out.println(" -> still scheduling: " + i); + } + w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); + } + + System.out.println("Waiting for the first round to finish..."); + cdl.await(); + } else { + for (int i = 0; i < n; i++) { + if (i % 50000 == 0) { + System.out.println(" -> still scheduling: " + i); + } + w.schedule(Functions.EMPTY_RUNNABLE, 1, TimeUnit.DAYS); + } + } + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long after = memHeap.getUsed(); + System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0); + + w.dispose(); + + System.out.println("Wait before second GC"); + System.out.println("JDK 6 purge is N log N because it removes and shifts one by one"); + int t = (int)(n * Math.log(n) / 100) + SchedulerPoolFactory.PURGE_PERIOD_SECONDS * 1000; + while (t > 0) { + System.out.printf(" >> Waiting for purge: %.2f s remaining%n", t / 1000d); + + System.gc(); + + Thread.sleep(1000); + + t -= 1000; + memHeap = memoryMXBean.getHeapMemoryUsage(); + long finish = memHeap.getUsed(); + System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0); + if (finish <= initial * 5) { + break; + } + } + + System.out.println("Second GC"); + System.gc(); + + Thread.sleep(1000); + + memHeap = memoryMXBean.getHeapMemoryUsage(); + long finish = memHeap.getUsed(); + System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0); + + if (finish > initial * 5) { + fail(String.format("Tasks retained: %.3f -> %.3f -> %.3f", initial / 1024 / 1024.0, after / 1024 / 1024.0, finish / 1024 / 1024d)); + } + } + + @Test(timeout = 60000) + public void testCancelledTaskRetention() throws InterruptedException { + ExecutorService exec = Executors.newSingleThreadExecutor(); + Scheduler s = Schedulers.from(exec, true); + try { + Scheduler.Worker w = s.createWorker(); + try { + testCancelledRetention(w, false); + } finally { + w.dispose(); + } + + w = s.createWorker(); + try { + testCancelledRetention(w, true); + } finally { + w.dispose(); + } + } finally { + exec.shutdownNow(); + } + } + + /** A simple executor which queues tasks and executes them one-by-one if executeOne() is called. */ + static final class TestExecutor implements Executor { + final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<Runnable>(); + @Override + public void execute(Runnable command) { + queue.offer(command); + } + public void executeOne() { + Runnable r = queue.poll(); + if (r != null) { + r.run(); + } + } + public void executeAll() { + Runnable r; + while ((r = queue.poll()) != null) { + r.run(); + } + } + } + + @Test + public void testCancelledTasksDontRun() { + final AtomicInteger calls = new AtomicInteger(); + Runnable task = new Runnable() { + @Override + public void run() { + calls.getAndIncrement(); + } + }; + TestExecutor exec = new TestExecutor(); + Scheduler custom = Schedulers.from(exec, true); + Worker w = custom.createWorker(); + try { + Disposable d1 = w.schedule(task); + Disposable d2 = w.schedule(task); + Disposable d3 = w.schedule(task); + + d1.dispose(); + d2.dispose(); + d3.dispose(); + + exec.executeAll(); + + assertEquals(0, calls.get()); + } finally { + w.dispose(); + } + } + + @Test + public void testCancelledWorkerDoesntRunTasks() { + final AtomicInteger calls = new AtomicInteger(); + Runnable task = new Runnable() { + @Override + public void run() { + calls.getAndIncrement(); + } + }; + TestExecutor exec = new TestExecutor(); + Scheduler custom = Schedulers.from(exec, true); + Worker w = custom.createWorker(); + try { + w.schedule(task); + w.schedule(task); + w.schedule(task); + } finally { + w.dispose(); + } + exec.executeAll(); + assertEquals(0, calls.get()); + } + + // FIXME the internal structure changed and these can't be tested +// +// @Test +// public void testNoTimedTaskAfterScheduleRetention() throws InterruptedException { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// command.run(); +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// w.schedule(Functions.emptyRunnable(), 50, TimeUnit.MILLISECONDS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// Thread.sleep(150); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } +// +// @Test +// public void testNoTimedTaskPartRetention() { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// Disposable task = w.schedule(Functions.emptyRunnable(), 1, TimeUnit.DAYS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// task.dispose(); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } +// +// @Test +// public void testNoPeriodicTimedTaskPartRetention() throws InterruptedException { +// Executor e = new Executor() { +// @Override +// public void execute(Runnable command) { +// command.run(); +// } +// }; +// ExecutorWorker w = (ExecutorWorker)Schedulers.from(e, true).createWorker(); +// +// final CountDownLatch cdl = new CountDownLatch(1); +// final Runnable action = new Runnable() { +// @Override +// public void run() { +// cdl.countDown(); +// } +// }; +// +// Disposable task = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); +// +// assertTrue(w.tasks.hasSubscriptions()); +// +// cdl.await(); +// +// task.dispose(); +// +// assertFalse(w.tasks.hasSubscriptions()); +// } + + @Test + public void plainExecutor() throws Exception { + Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + r.run(); + } + }, true); + + final CountDownLatch cdl = new CountDownLatch(5); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.scheduleDirect(r); + + s.scheduleDirect(r, 50, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodicallyDirect(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + + assertTrue(d.isDisposed()); + } + + @Test + public void rejectingExecutor() { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + exec.shutdown(); + + Scheduler s = Schedulers.from(exec, true); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + assertSame(EmptyDisposable.INSTANCE, s.scheduleDirect(Functions.EMPTY_RUNNABLE)); + + assertSame(EmptyDisposable.INSTANCE, s.scheduleDirect(Functions.EMPTY_RUNNABLE, 10, TimeUnit.MILLISECONDS)); + + assertSame(EmptyDisposable.INSTANCE, s.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 10, 10, TimeUnit.MILLISECONDS)); + + TestHelper.assertUndeliverable(errors, 0, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 1, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 2, RejectedExecutionException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void rejectingExecutorWorker() { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + exec.shutdown(); + + List<Throwable> errors = TestHelper.trackPluginErrors(); + + try { + Worker s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedule(Functions.EMPTY_RUNNABLE)); + + s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedule(Functions.EMPTY_RUNNABLE, 10, TimeUnit.MILLISECONDS)); + + s = Schedulers.from(exec, true).createWorker(); + assertSame(EmptyDisposable.INSTANCE, s.schedulePeriodically(Functions.EMPTY_RUNNABLE, 10, 10, TimeUnit.MILLISECONDS)); + + TestHelper.assertUndeliverable(errors, 0, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 1, RejectedExecutionException.class); + TestHelper.assertUndeliverable(errors, 2, RejectedExecutionException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void reuseScheduledExecutor() throws Exception { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + + try { + Scheduler s = Schedulers.from(exec, true); + + final CountDownLatch cdl = new CountDownLatch(8); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.scheduleDirect(r); + + s.scheduleDirect(r, 10, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodicallyDirect(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + } finally { + exec.shutdown(); + } + } + + @Test + public void reuseScheduledExecutorAsWorker() throws Exception { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + + Worker s = Schedulers.from(exec, true).createWorker(); + + assertFalse(s.isDisposed()); + try { + + final CountDownLatch cdl = new CountDownLatch(8); + + Runnable r = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + + s.schedule(r); + + s.schedule(r, 10, TimeUnit.MILLISECONDS); + + Disposable d = s.schedulePeriodically(r, 10, 10, TimeUnit.MILLISECONDS); + + try { + assertTrue(cdl.await(5, TimeUnit.SECONDS)); + } finally { + d.dispose(); + } + } finally { + s.dispose(); + exec.shutdown(); + } + + assertTrue(s.isDisposed()); + } + + @Test + public void disposeRace() { + ExecutorService exec = Executors.newSingleThreadExecutor(); + final Scheduler s = Schedulers.from(exec, true); + try { + for (int i = 0; i < 500; i++) { + final Worker w = s.createWorker(); + + final AtomicInteger c = new AtomicInteger(2); + + w.schedule(new Runnable() { + @Override + public void run() { + c.decrementAndGet(); + while (c.get() != 0) { } + } + }); + + c.decrementAndGet(); + while (c.get() != 0) { } + w.dispose(); + } + } finally { + exec.shutdownNow(); + } + } + + @Test + public void runnableDisposed() { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + r.run(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + assertTrue(d.isDisposed()); + } + + @Test(timeout = 1000) + public void runnableDisposedAsync() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsync2() throws Exception { + final Scheduler s = Schedulers.from(executor, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncCrash() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(new Runnable() { + @Override + public void run() { + throw new IllegalStateException(); + } + }); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncTimed() throws Exception { + final Scheduler s = Schedulers.from(new Executor() { + @Override + public void execute(Runnable r) { + new Thread(r).start(); + } + }, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MILLISECONDS); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } + + @Test(timeout = 1000) + public void runnableDisposedAsyncTimed2() throws Exception { + ExecutorService executorScheduler = Executors.newScheduledThreadPool(1, new RxThreadFactory("TestCustomPoolTimed")); + try { + final Scheduler s = Schedulers.from(executorScheduler, true); + Disposable d = s.scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MILLISECONDS); + + while (!d.isDisposed()) { + Thread.sleep(1); + } + } finally { + executorScheduler.shutdownNow(); + } + } + + @Test + public void unwrapScheduleDirectTaskAfterDispose() { + Scheduler scheduler = getScheduler(); + final CountDownLatch cdl = new CountDownLatch(1); + Runnable countDownRunnable = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + Disposable disposable = scheduler.scheduleDirect(countDownRunnable, 100, TimeUnit.MILLISECONDS); + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) disposable; + assertSame(countDownRunnable, wrapper.getWrappedRunnable()); + disposable.dispose(); + + assertSame(Functions.EMPTY_RUNNABLE, wrapper.getWrappedRunnable()); + } + + @Test(timeout = 10000) + public void interruptibleDirectTask() throws Exception { + Scheduler scheduler = getScheduler(); + + final AtomicInteger sync = new AtomicInteger(2); + + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + Disposable d = scheduler.scheduleDirect(new Runnable() { + @Override + public void run() { + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + } + }); + + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + + Thread.sleep(500); + + d.dispose(); + + int i = 20; + while (i-- > 0 && !isInterrupted.get()) { + Thread.sleep(50); + } + + assertTrue("Interruption did not propagate", isInterrupted.get()); + } + + @Test(timeout = 10000) + public void interruptibleWorkerTask() throws Exception { + Scheduler scheduler = getScheduler(); + + Worker worker = scheduler.createWorker(); + + try { + final AtomicInteger sync = new AtomicInteger(2); + + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + Disposable d = worker.schedule(new Runnable() { + @Override + public void run() { + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + } + }); + + if (sync.decrementAndGet() != 0) { + while (sync.get() != 0) { } + } + + Thread.sleep(500); + + d.dispose(); + + int i = 20; + while (i-- > 0 && !isInterrupted.get()) { + Thread.sleep(50); + } + + assertTrue("Interruption did not propagate", isInterrupted.get()); + } finally { + worker.dispose(); + } + } +} diff --git a/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java index c0a19efa5e..eaa5b692f1 100644 --- a/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/ExecutorSchedulerTest.java @@ -59,7 +59,6 @@ public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) Thread.sleep(1000); - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); long initial = memHeap.getUsed(); @@ -189,13 +188,13 @@ public void run() { Scheduler custom = Schedulers.from(exec); Worker w = custom.createWorker(); try { - Disposable s1 = w.schedule(task); - Disposable s2 = w.schedule(task); - Disposable s3 = w.schedule(task); + Disposable d1 = w.schedule(task); + Disposable d2 = w.schedule(task); + Disposable d3 = w.schedule(task); - s1.dispose(); - s2.dispose(); - s3.dispose(); + d1.dispose(); + d2.dispose(); + d3.dispose(); exec.executeAll(); @@ -204,6 +203,7 @@ public void run() { w.dispose(); } } + @Test public void testCancelledWorkerDoesntRunTasks() { final AtomicInteger calls = new AtomicInteger(); @@ -258,11 +258,11 @@ public void run() { // }; // ExecutorWorker w = (ExecutorWorker)Schedulers.from(e).createWorker(); // -// Disposable s = w.schedule(Functions.emptyRunnable(), 1, TimeUnit.DAYS); +// Disposable task = w.schedule(Functions.emptyRunnable(), 1, TimeUnit.DAYS); // // assertTrue(w.tasks.hasSubscriptions()); // -// s.dispose(); +// task.dispose(); // // assertFalse(w.tasks.hasSubscriptions()); // } @@ -285,13 +285,13 @@ public void run() { // } // }; // -// Disposable s = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); +// Disposable task = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS); // // assertTrue(w.tasks.hasSubscriptions()); // // cdl.await(); // -// s.dispose(); +// task.dispose(); // // assertFalse(w.tasks.hasSubscriptions()); // } @@ -561,4 +561,22 @@ public void runnableDisposedAsyncTimed2() throws Exception { executorScheduler.shutdownNow(); } } + + @Test + public void unwrapScheduleDirectTaskAfterDispose() { + Scheduler scheduler = getScheduler(); + final CountDownLatch cdl = new CountDownLatch(1); + Runnable countDownRunnable = new Runnable() { + @Override + public void run() { + cdl.countDown(); + } + }; + Disposable disposable = scheduler.scheduleDirect(countDownRunnable, 100, TimeUnit.MILLISECONDS); + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) disposable; + assertSame(countDownRunnable, wrapper.getWrappedRunnable()); + disposable.dispose(); + + assertSame(Functions.EMPTY_RUNNABLE, wrapper.getWrappedRunnable()); + } } diff --git a/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java b/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java index 66ed876967..aa0af9816b 100644 --- a/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java +++ b/src/test/java/io/reactivex/schedulers/SchedulerLifecycleTest.java @@ -73,31 +73,30 @@ public void run() { } }; - CompositeDisposable csub = new CompositeDisposable(); + CompositeDisposable cd = new CompositeDisposable(); try { Worker w1 = Schedulers.computation().createWorker(); - csub.add(w1); + cd.add(w1); w1.schedule(countAction); Worker w2 = Schedulers.io().createWorker(); - csub.add(w2); + cd.add(w2); w2.schedule(countAction); Worker w3 = Schedulers.newThread().createWorker(); - csub.add(w3); + cd.add(w3); w3.schedule(countAction); Worker w4 = Schedulers.single().createWorker(); - csub.add(w4); + cd.add(w4); w4.schedule(countAction); - if (!cdl.await(3, TimeUnit.SECONDS)) { fail("countAction was not run by every worker"); } } finally { - csub.dispose(); + cd.dispose(); } } diff --git a/src/test/java/io/reactivex/schedulers/SchedulerTest.java b/src/test/java/io/reactivex/schedulers/SchedulerTest.java index 2845cf3a91..99ffca6afb 100644 --- a/src/test/java/io/reactivex/schedulers/SchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/SchedulerTest.java @@ -182,7 +182,7 @@ public void run() { public void periodicDirectTaskRace() { final TestScheduler scheduler = new TestScheduler(); - for (int i = 0; i < 100; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Disposable d = scheduler.schedulePeriodicallyDirect(Functions.EMPTY_RUNNABLE, 1, 1, TimeUnit.MILLISECONDS); Runnable r1 = new Runnable() { @@ -199,12 +199,11 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.io()); + TestHelper.race(r1, r2); } } - @Test public void periodicDirectTaskRaceIO() throws Exception { final Scheduler scheduler = Schedulers.io(); @@ -307,4 +306,66 @@ public void customScheduleDirectDisposed() { assertTrue(d.isDisposed()); } + + @Test + public void unwrapDefaultPeriodicTask() { + TestScheduler scheduler = new TestScheduler(); + + Runnable runnable = new Runnable() { + @Override + public void run() { + } + }; + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) scheduler.schedulePeriodicallyDirect(runnable, 100, 100, TimeUnit.MILLISECONDS); + + assertSame(runnable, wrapper.getWrappedRunnable()); + } + + @Test + public void unwrapScheduleDirectTask() { + TestScheduler scheduler = new TestScheduler(); + + Runnable runnable = new Runnable() { + @Override + public void run() { + } + }; + SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) scheduler.scheduleDirect(runnable, 100, TimeUnit.MILLISECONDS); + assertSame(runnable, wrapper.getWrappedRunnable()); + } + + @Test + public void unwrapWorkerPeriodicTask() { + final Runnable runnable = new Runnable() { + @Override + public void run() { + } + }; + + Scheduler scheduler = new Scheduler() { + @Override + public Worker createWorker() { + return new Worker() { + @Override + public Disposable schedule(Runnable run, long delay, TimeUnit unit) { + SchedulerRunnableIntrospection outerWrapper = (SchedulerRunnableIntrospection) run; + SchedulerRunnableIntrospection innerWrapper = (SchedulerRunnableIntrospection) outerWrapper.getWrappedRunnable(); + assertSame(runnable, innerWrapper.getWrappedRunnable()); + return (Disposable) innerWrapper; + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + }; + } + }; + + scheduler.schedulePeriodicallyDirect(runnable, 100, 100, TimeUnit.MILLISECONDS); + } } diff --git a/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java b/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java index 803bd50522..4139365384 100644 --- a/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/TestSchedulerTest.java @@ -232,9 +232,9 @@ public void timedRunnableToString() { TimedRunnable r = new TimedRunnable((TestWorker) new TestScheduler().createWorker(), 5, new Runnable() { @Override public void run() { - // TODO Auto-generated method stub - + // deliberately no-op } + @Override public String toString() { return "Runnable"; @@ -253,5 +253,10 @@ public void workerDisposed() { assertTrue(w.isDisposed()); } - + @Test + public void constructorTimeSetsTime() { + TestScheduler ts = new TestScheduler(5, TimeUnit.SECONDS); + assertEquals(5, ts.now(TimeUnit.SECONDS)); + assertEquals(5000, ts.now(TimeUnit.MILLISECONDS)); + } } diff --git a/src/test/java/io/reactivex/schedulers/TimedTest.java b/src/test/java/io/reactivex/schedulers/TimedTest.java index 7e8cb41e1a..c380188665 100644 --- a/src/test/java/io/reactivex/schedulers/TimedTest.java +++ b/src/test/java/io/reactivex/schedulers/TimedTest.java @@ -75,7 +75,7 @@ public void equalsWith() { assertNotEquals(new Object(), t1); - assertFalse(t1.equals(new Object())); + assertNotEquals(t1, new Object()); } @Test diff --git a/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java b/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java index df40ea63fd..9f651bb50e 100644 --- a/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java @@ -31,7 +31,6 @@ import org.reactivestreams.Subscriber; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; public class TrampolineSchedulerTest extends AbstractSchedulerTests { @@ -45,18 +44,18 @@ public final void testMergeWithCurrentThreadScheduler1() { final String currentThreadName = Thread.currentThread().getName(); - Flowable<Integer> o1 = Flowable.<Integer> just(1, 2, 3, 4, 5); - Flowable<Integer> o2 = Flowable.<Integer> just(6, 7, 8, 9, 10); - Flowable<String> o = Flowable.<Integer> merge(o1, o2).subscribeOn(Schedulers.trampoline()).map(new Function<Integer, String>() { + Flowable<Integer> f1 = Flowable.<Integer> just(1, 2, 3, 4, 5); + Flowable<Integer> f2 = Flowable.<Integer> just(6, 7, 8, 9, 10); + Flowable<String> f = Flowable.<Integer> merge(f1, f2).subscribeOn(Schedulers.trampoline()).map(new Function<Integer, String>() { @Override public String apply(Integer t) { - assertTrue(Thread.currentThread().getName().equals(currentThreadName)); + assertEquals(Thread.currentThread().getName(), currentThreadName); return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); } }); - o.blockingForEach(new Consumer<String>() { + f.blockingForEach(new Consumer<String>() { @Override public void accept(String t) { @@ -110,8 +109,8 @@ public void run() { @Test public void testTrampolineWorkerHandlesConcurrentScheduling() { final Worker trampolineWorker = Schedulers.trampoline().createWorker(); - final Subscriber<Object> observer = TestHelper.mockSubscriber(); - final TestSubscriber<Disposable> ts = new TestSubscriber<Disposable>(observer); + final Subscriber<Object> subscriber = TestHelper.mockSubscriber(); + final TestSubscriber<Disposable> ts = new TestSubscriber<Disposable>(subscriber); // Spam the trampoline with actions. Flowable.range(0, 50) diff --git a/src/test/java/io/reactivex/single/SingleCacheTest.java b/src/test/java/io/reactivex/single/SingleCacheTest.java index acdf75679a..ddfa964ac0 100644 --- a/src/test/java/io/reactivex/single/SingleCacheTest.java +++ b/src/test/java/io/reactivex/single/SingleCacheTest.java @@ -55,15 +55,15 @@ public void delayed() { PublishSubject<Integer> ps = PublishSubject.create(); Single<Integer> cache = ps.single(-99).cache(); - TestObserver<Integer> ts1 = cache.test(); + TestObserver<Integer> to1 = cache.test(); - TestObserver<Integer> ts2 = cache.test(); + TestObserver<Integer> to2 = cache.test(); ps.onNext(1); ps.onComplete(); - ts1.assertResult(1); - ts2.assertResult(1); + to1.assertResult(1); + to2.assertResult(1); } @Test @@ -71,17 +71,17 @@ public void delayedDisposed() { PublishSubject<Integer> ps = PublishSubject.create(); Single<Integer> cache = ps.single(-99).cache(); - TestObserver<Integer> ts1 = cache.test(); + TestObserver<Integer> to1 = cache.test(); - TestObserver<Integer> ts2 = cache.test(); + TestObserver<Integer> to2 = cache.test(); - ts1.cancel(); + to1.cancel(); ps.onNext(1); ps.onComplete(); - ts1.assertNoValues().assertNoErrors().assertNotComplete(); - ts2.assertResult(1); + to1.assertNoValues().assertNoErrors().assertNotComplete(); + to2.assertResult(1); } @Test diff --git a/src/test/java/io/reactivex/single/SingleNullTests.java b/src/test/java/io/reactivex/single/SingleNullTests.java index 405054243c..059f106ffe 100644 --- a/src/test/java/io/reactivex/single/SingleNullTests.java +++ b/src/test/java/io/reactivex/single/SingleNullTests.java @@ -13,7 +13,6 @@ package io.reactivex.single; - import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; @@ -23,7 +22,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.SingleOperator; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; @@ -664,7 +662,7 @@ public void liftNull() { public void liftFunctionReturnsNull() { just1.lift(new SingleOperator<Object, Integer>() { @Override - public SingleObserver<? super Integer> apply(SingleObserver<? super Object> s) { + public SingleObserver<? super Integer> apply(SingleObserver<? super Object> observer) { return null; } }).blockingGet(); @@ -809,6 +807,7 @@ public void subscribeOnErrorNull() { public void accept(Integer v) { } }, null); } + @Test(expected = NullPointerException.class) public void subscribeSubscriberNull() { just1.toFlowable().subscribe((Subscriber<Integer>)null); @@ -844,6 +843,11 @@ public void toNull() { just1.to(null); } + @Test(expected = NullPointerException.class) + public void asNull() { + just1.as(null); + } + @Test(expected = NullPointerException.class) public void zipWithNull() { just1.zipWith(null, new BiFunction<Integer, Object, Object>() { diff --git a/src/test/java/io/reactivex/single/SingleRetryTest.java b/src/test/java/io/reactivex/single/SingleRetryTest.java new file mode 100644 index 0000000000..c37f877f03 --- /dev/null +++ b/src/test/java/io/reactivex/single/SingleRetryTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2017-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.single; + +import io.reactivex.Single; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.functions.Functions; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SingleRetryTest { + @Test + public void retryTimesPredicateWithMatchingPredicate() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Single.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + throw new IllegalArgumentException(); + } + }) + .retry(Integer.MAX_VALUE, new Predicate<Throwable>() { + @Override public boolean test(final Throwable throwable) throws Exception { + return !(throwable instanceof IllegalArgumentException); + } + }) + .test() + .assertFailure(IllegalArgumentException.class); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Single.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(2, Functions.alwaysTrue()) + .test() + .assertResult(true); + + assertEquals(3, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithNotMatchingRetryAmount() { + final AtomicInteger atomicInteger = new AtomicInteger(3); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Single.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(1, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(2, numberOfSubscribeCalls.get()); + } + + @Test + public void retryTimesPredicateWithZeroRetries() { + final AtomicInteger atomicInteger = new AtomicInteger(2); + final AtomicInteger numberOfSubscribeCalls = new AtomicInteger(0); + + Single.fromCallable(new Callable<Boolean>() { + @Override public Boolean call() throws Exception { + numberOfSubscribeCalls.incrementAndGet(); + + if (atomicInteger.decrementAndGet() != 0) { + throw new RuntimeException(); + } + + return true; + } + }) + .retry(0, Functions.alwaysTrue()) + .test() + .assertFailure(RuntimeException.class); + + assertEquals(1, numberOfSubscribeCalls.get()); + } +} diff --git a/src/test/java/io/reactivex/single/SingleTest.java b/src/test/java/io/reactivex/single/SingleTest.java index 3fc650d276..9e3d136c6d 100644 --- a/src/test/java/io/reactivex/single/SingleTest.java +++ b/src/test/java/io/reactivex/single/SingleTest.java @@ -131,9 +131,9 @@ public void testCreateSuccess() { Single.unsafeCreate(new SingleSource<Object>() { @Override - public void subscribe(SingleObserver<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onSuccess("Hello"); + public void subscribe(SingleObserver<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSuccess("Hello"); } }).toFlowable().subscribe(ts); @@ -145,9 +145,9 @@ public void testCreateError() { TestSubscriber<Object> ts = new TestSubscriber<Object>(); Single.unsafeCreate(new SingleSource<Object>() { @Override - public void subscribe(SingleObserver<? super Object> s) { - s.onSubscribe(Disposables.empty()); - s.onError(new RuntimeException("fail")); + public void subscribe(SingleObserver<? super Object> observer) { + observer.onSubscribe(Disposables.empty()); + observer.onError(new RuntimeException("fail")); } }).toFlowable().subscribe(ts); @@ -202,14 +202,14 @@ public void testTimeout() { TestSubscriber<String> ts = new TestSubscriber<String>(); Single<String> s1 = Single.<String>unsafeCreate(new SingleSource<String>() { @Override - public void subscribe(SingleObserver<? super String> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(SingleObserver<? super String> observer) { + observer.onSubscribe(Disposables.empty()); try { Thread.sleep(5000); } catch (InterruptedException e) { // ignore as we expect this for the test } - s.onSuccess("success"); + observer.onSuccess("success"); } }).subscribeOn(Schedulers.io()); @@ -224,14 +224,14 @@ public void testTimeoutWithFallback() { TestSubscriber<String> ts = new TestSubscriber<String>(); Single<String> s1 = Single.<String>unsafeCreate(new SingleSource<String>() { @Override - public void subscribe(SingleObserver<? super String> s) { - s.onSubscribe(Disposables.empty()); + public void subscribe(SingleObserver<? super String> observer) { + observer.onSubscribe(Disposables.empty()); try { Thread.sleep(5000); } catch (InterruptedException e) { // ignore as we expect this for the test } - s.onSuccess("success"); + observer.onSuccess("success"); } }).subscribeOn(Schedulers.io()); @@ -251,16 +251,16 @@ public void testUnsubscribe() throws InterruptedException { Single<String> s1 = Single.<String>unsafeCreate(new SingleSource<String>() { @Override - public void subscribe(final SingleObserver<? super String> s) { + public void subscribe(final SingleObserver<? super String> observer) { SerialDisposable sd = new SerialDisposable(); - s.onSubscribe(sd); + observer.onSubscribe(sd); final Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); - s.onSuccess("success"); + observer.onSuccess("success"); } catch (InterruptedException e) { interrupted.set(true); latch.countDown(); @@ -325,16 +325,16 @@ public void onError(Throwable error) { Single<String> s1 = Single.unsafeCreate(new SingleSource<String>() { @Override - public void subscribe(final SingleObserver<? super String> s) { + public void subscribe(final SingleObserver<? super String> observer) { SerialDisposable sd = new SerialDisposable(); - s.onSubscribe(sd); + observer.onSubscribe(sd); final Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); - s.onSuccess("success"); + observer.onSuccess("success"); } catch (InterruptedException e) { interrupted.set(true); latch.countDown(); @@ -381,16 +381,16 @@ public void testUnsubscribeViaReturnedSubscription() throws InterruptedException Single<String> s1 = Single.unsafeCreate(new SingleSource<String>() { @Override - public void subscribe(final SingleObserver<? super String> s) { + public void subscribe(final SingleObserver<? super String> observer) { SerialDisposable sd = new SerialDisposable(); - s.onSubscribe(sd); + observer.onSubscribe(sd); final Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); - s.onSuccess("success"); + observer.onSuccess("success"); } catch (InterruptedException e) { interrupted.set(true); latch.countDown(); @@ -543,6 +543,18 @@ public Integer apply(Single<Integer> v) throws Exception { }).intValue()); } + @Test + public void as() { + Single.just(1).as(new SingleConverter<Integer, Flowable<Integer>>() { + @Override + public Flowable<Integer> apply(Single<Integer> v) { + return v.toFlowable(); + } + }) + .test() + .assertResult(1); + } + @Test(expected = NullPointerException.class) public void fromObservableNull() { Single.fromObservable(null); diff --git a/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java b/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java index 8332c0cb87..c1be4ab34c 100644 --- a/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -27,14 +26,18 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Consumer; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; -import io.reactivex.schedulers.Schedulers; -public class AsyncSubjectTest { +public class AsyncSubjectTest extends SubjectTest<Integer> { private final Throwable testException = new Throwable(); + @Override + protected Subject<Integer> create() { + return AsyncSubject.create(); + } + @Test public void testNeverCompleted() { AsyncSubject<String> subject = AsyncSubject.create(); @@ -147,13 +150,13 @@ public void testUnsubscribeBeforeCompleted() { AsyncSubject<String> subject = AsyncSubject.create(); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - subject.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + subject.subscribe(to); subject.onNext("one"); subject.onNext("two"); - ts.dispose(); + to.dispose(); verify(observer, Mockito.never()).onNext(anyString()); verify(observer, Mockito.never()).onError(any(Throwable.class)); @@ -278,8 +281,8 @@ public void run() { // AsyncSubject<String> ps = AsyncSubject.create(); // // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // // try { // ps.onError(new RuntimeException("an exception")); @@ -288,10 +291,9 @@ public void run() { // // ignore // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } - // FIXME subscriber methods are not allowed to throw // /** // * This one has multiple failures so should get a CompositeException @@ -302,8 +304,8 @@ public void run() { // // ps.subscribe(); // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // ps.subscribe(); // ps.subscribe(); // ps.subscribe(); @@ -316,7 +318,7 @@ public void run() { // assertEquals(5, e.getExceptions().size()); // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } @Test @@ -363,6 +365,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { AsyncSubject<Object> as = AsyncSubject.create(); @@ -382,28 +385,27 @@ public void testCurrentStateMethodsError() { assertTrue(as.getThrowable() instanceof TestException); } - @Test public void fusionLive() { AsyncSubject<Integer> ap = new AsyncSubject<Integer>(); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - ap.subscribe(ts); + ap.subscribe(to); - ts + to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)); + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)); - ts.assertNoValues().assertNoErrors().assertNotComplete(); + to.assertNoValues().assertNoErrors().assertNotComplete(); ap.onNext(1); - ts.assertNoValues().assertNoErrors().assertNotComplete(); + to.assertNoValues().assertNoErrors().assertNotComplete(); ap.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -412,68 +414,16 @@ public void fusionOfflie() { ap.onNext(1); ap.onComplete(); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueSubscription.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - ap.subscribe(ts); + ap.subscribe(to); - ts + to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1); } - @Test - public void onNextNull() { - final AsyncSubject<Object> s = AsyncSubject.create(); - - s.onNext(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNull() { - final AsyncSubject<Object> s = AsyncSubject.create(); - - s.onError(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onNextNullDelayed() { - final AsyncSubject<Object> p = AsyncSubject.create(); - - TestObserver<Object> ts = p.test(); - - p.onNext(null); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNullDelayed() { - final AsyncSubject<Object> p = AsyncSubject.create(); - - TestObserver<Object> ts = p.test(); - - p.onError(null); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - @Test public void onSubscribeAfterDone() { AsyncSubject<Object> p = AsyncSubject.create(); @@ -511,40 +461,40 @@ public void cancelUpfront() { public void cancelRace() { AsyncSubject<Object> p = AsyncSubject.create(); - for (int i = 0; i < 500; i++) { - final TestObserver<Object> ts1 = p.test(); - final TestObserver<Object> ts2 = p.test(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestObserver<Object> to1 = p.test(); + final TestObserver<Object> to2 = p.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ts2.cancel(); + to2.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @Test public void onErrorCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final AsyncSubject<Object> p = AsyncSubject.create(); - final TestObserver<Object> ts1 = p.test(); + final TestObserver<Object> to1 = p.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; @@ -557,12 +507,12 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - if (ts1.errorCount() != 0) { - ts1.assertFailure(TestException.class); + if (to1.errorCount() != 0) { + to1.assertFailure(TestException.class); } else { - ts1.assertEmpty(); + to1.assertEmpty(); } } } @@ -571,66 +521,66 @@ public void run() { public void onNextCrossCancel() { AsyncSubject<Object> p = AsyncSubject.create(); - final TestObserver<Object> ts2 = new TestObserver<Object>(); - TestObserver<Object> ts1 = new TestObserver<Object>() { + final TestObserver<Object> to2 = new TestObserver<Object>(); + TestObserver<Object> to1 = new TestObserver<Object>() { @Override public void onNext(Object t) { - ts2.cancel(); + to2.cancel(); super.onNext(t); } }; - p.subscribe(ts1); - p.subscribe(ts2); + p.subscribe(to1); + p.subscribe(to2); p.onNext(1); p.onComplete(); - ts1.assertResult(1); - ts2.assertEmpty(); + to1.assertResult(1); + to2.assertEmpty(); } @Test public void onErrorCrossCancel() { AsyncSubject<Object> p = AsyncSubject.create(); - final TestObserver<Object> ts2 = new TestObserver<Object>(); - TestObserver<Object> ts1 = new TestObserver<Object>() { + final TestObserver<Object> to2 = new TestObserver<Object>(); + TestObserver<Object> to1 = new TestObserver<Object>() { @Override public void onError(Throwable t) { - ts2.cancel(); + to2.cancel(); super.onError(t); } }; - p.subscribe(ts1); - p.subscribe(ts2); + p.subscribe(to1); + p.subscribe(to2); p.onError(new TestException()); - ts1.assertFailure(TestException.class); - ts2.assertEmpty(); + to1.assertFailure(TestException.class); + to2.assertEmpty(); } @Test public void onCompleteCrossCancel() { AsyncSubject<Object> p = AsyncSubject.create(); - final TestObserver<Object> ts2 = new TestObserver<Object>(); - TestObserver<Object> ts1 = new TestObserver<Object>() { + final TestObserver<Object> to2 = new TestObserver<Object>(); + TestObserver<Object> to1 = new TestObserver<Object>() { @Override public void onComplete() { - ts2.cancel(); + to2.cancel(); super.onComplete(); } }; - p.subscribe(ts1); - p.subscribe(ts2); + p.subscribe(to1); + p.subscribe(to2); p.onComplete(); - ts1.assertResult(); - ts2.assertEmpty(); + to1.assertResult(); + to2.assertEmpty(); } } diff --git a/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java b/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java index 62941ef5e0..1a6f48a0e7 100644 --- a/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; @@ -31,11 +30,17 @@ import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.BehaviorSubject.BehaviorDisposable; -public class BehaviorSubjectTest { +public class BehaviorSubjectTest extends SubjectTest<Integer> { private final Throwable testException = new Throwable(); + @Override + protected Subject<Integer> create() { + return BehaviorSubject.create(); + } + @Test public void testThatSubscriberReceivesDefaultValueAndSubsequentEvents() { BehaviorSubject<String> subject = BehaviorSubject.createDefault("default"); @@ -129,9 +134,9 @@ public void testCompletedStopsEmittingData() { Observer<Object> observerB = TestHelper.mockObserver(); Observer<Object> observerC = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(observerA); + TestObserver<Object> to = new TestObserver<Object>(observerA); - channel.subscribe(ts); + channel.subscribe(to); channel.subscribe(observerB); InOrder inOrderA = inOrder(observerA); @@ -146,7 +151,7 @@ public void testCompletedStopsEmittingData() { inOrderA.verify(observerA).onNext(42); inOrderB.verify(observerB).onNext(42); - ts.dispose(); + to.dispose(); inOrderA.verifyNoMoreInteractions(); channel.onNext(4711); @@ -276,6 +281,7 @@ public void onComplete() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testStartEmpty() { BehaviorSubject<Integer> source = BehaviorSubject.create(); @@ -298,9 +304,8 @@ public void testStartEmpty() { inOrder.verify(o).onNext(1); inOrder.verify(o).onComplete(); inOrder.verifyNoMoreInteractions(); - - } + @Test public void testStartEmptyThenAddOne() { BehaviorSubject<Integer> source = BehaviorSubject.create(); @@ -323,6 +328,7 @@ public void testStartEmptyThenAddOne() { verify(o, never()).onError(any(Throwable.class)); } + @Test public void testStartEmptyCompleteWithOne() { BehaviorSubject<Integer> source = BehaviorSubject.create(); @@ -361,8 +367,8 @@ public void testTakeOneSubscriber() { // BehaviorSubject<String> ps = BehaviorSubject.create(); // // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<T>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<T>(); +// ps.subscribe(to); // // try { // ps.onError(new RuntimeException("an exception")); @@ -371,7 +377,7 @@ public void testTakeOneSubscriber() { // // ignore // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } // FIXME RS subscribers are not allowed to throw @@ -384,8 +390,8 @@ public void testTakeOneSubscriber() { // // ps.subscribe(); // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // ps.subscribe(); // ps.subscribe(); // ps.subscribe(); @@ -398,8 +404,9 @@ public void testTakeOneSubscriber() { // assertEquals(5, e.getExceptions().size()); // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } + @Test public void testEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -544,6 +551,7 @@ public void testCurrentStateMethodsEmpty() { assertNull(as.getValue()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { BehaviorSubject<Object> as = BehaviorSubject.create(); @@ -563,66 +571,6 @@ public void testCurrentStateMethodsError() { assertTrue(as.getThrowable() instanceof TestException); } - @Test - public void onNextNull() { - final BehaviorSubject<Object> s = BehaviorSubject.create(); - - s.onNext(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNull() { - final BehaviorSubject<Object> s = BehaviorSubject.create(); - - s.onError(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onNextNullDelayed() { - final BehaviorSubject<Object> p = BehaviorSubject.create(); - - TestObserver<Object> ts = p.test(); - - assertTrue(p.hasObservers()); - - p.onNext(null); - - assertFalse(p.hasObservers()); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNullDelayed() { - final BehaviorSubject<Object> p = BehaviorSubject.create(); - - TestObserver<Object> ts = p.test(); - - assertTrue(p.hasObservers()); - - p.onError(null); - - assertFalse(p.hasObservers()); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - @Test public void cancelOnArrival() { BehaviorSubject<Object> p = BehaviorSubject.create(); @@ -673,22 +621,22 @@ public void onErrorAfterComplete() { public void cancelOnArrival2() { BehaviorSubject<Object> p = BehaviorSubject.create(); - TestObserver<Object> ts = p.test(); + TestObserver<Object> to = p.test(); p.test(true).assertEmpty(); p.onNext(1); p.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorSubject<Object> p = BehaviorSubject.create(); - final TestObserver<Object> ts = p.test(); + final TestObserver<Object> to = p.test(); Runnable r1 = new Runnable() { @Override @@ -700,26 +648,26 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void subscribeOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorSubject<Object> p = BehaviorSubject.createDefault((Object)1); - final TestObserver[] ts = { null }; + final TestObserver[] to = { null }; Runnable r1 = new Runnable() { @Override public void run() { - ts[0] = p.test(); + to[0] = p.test(); } }; @@ -730,12 +678,12 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - if (ts[0].valueCount() == 1) { - ts[0].assertValue(2).assertNoErrors().assertNotComplete(); + if (to[0].valueCount() == 1) { + to[0].assertValue(2).assertNoErrors().assertNotComplete(); } else { - ts[0].assertValues(1, 2).assertNoErrors().assertNotComplete(); + to[0].assertValues(1, 2).assertNoErrors().assertNotComplete(); } } } @@ -770,18 +718,17 @@ public void onComplete() { }); } - @Test public void completeSubscribeRace() throws Exception { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorSubject<Object> p = BehaviorSubject.create(); - final TestObserver<Object> ts = new TestObserver<Object>(); + final TestObserver<Object> to = new TestObserver<Object>(); Runnable r1 = new Runnable() { @Override public void run() { - p.subscribe(ts); + p.subscribe(to); } }; @@ -794,23 +741,23 @@ public void run() { TestHelper.race(r1, r2); - ts.assertResult(); + to.assertResult(); } } @Test public void errorSubscribeRace() throws Exception { - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final BehaviorSubject<Object> p = BehaviorSubject.create(); - final TestObserver<Object> ts = new TestObserver<Object>(); + final TestObserver<Object> to = new TestObserver<Object>(); final TestException ex = new TestException(); Runnable r1 = new Runnable() { @Override public void run() { - p.subscribe(ts); + p.subscribe(to); } }; @@ -823,7 +770,111 @@ public void run() { TestHelper.race(r1, r2); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); } } + + @Test + public void behaviorDisposableDisposeState() { + BehaviorSubject<Integer> bs = BehaviorSubject.create(); + bs.onNext(1); + + TestObserver<Integer> to = new TestObserver<Integer>(); + + BehaviorDisposable<Integer> bd = new BehaviorDisposable<Integer>(to, bs); + to.onSubscribe(bd); + + assertFalse(bd.isDisposed()); + + bd.dispose(); + + assertTrue(bd.isDisposed()); + + bd.dispose(); + + assertTrue(bd.isDisposed()); + + assertTrue(bd.test(2)); + + bd.emitFirst(); + + to.assertEmpty(); + + bd.emitNext(2, 0); + } + + @Test + public void emitFirstDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + BehaviorSubject<Integer> bs = BehaviorSubject.create(); + bs.onNext(1); + + TestObserver<Integer> to = new TestObserver<Integer>(); + + final BehaviorDisposable<Integer> bd = new BehaviorDisposable<Integer>(to, bs); + to.onSubscribe(bd); + + Runnable r1 = new Runnable() { + @Override + public void run() { + bd.emitFirst(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + bd.dispose(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void emitNextDisposeRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + BehaviorSubject<Integer> bs = BehaviorSubject.create(); + bs.onNext(1); + + TestObserver<Integer> to = new TestObserver<Integer>(); + + final BehaviorDisposable<Integer> bd = new BehaviorDisposable<Integer>(to, bs); + to.onSubscribe(bd); + + Runnable r1 = new Runnable() { + @Override + public void run() { + bd.emitNext(2, 0); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + bd.dispose(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void emittingEmitNext() { + BehaviorSubject<Integer> bs = BehaviorSubject.create(); + bs.onNext(1); + + TestObserver<Integer> to = new TestObserver<Integer>(); + + final BehaviorDisposable<Integer> bd = new BehaviorDisposable<Integer>(to, bs); + to.onSubscribe(bd); + + bd.emitting = true; + bd.emitNext(2, 1); + bd.emitNext(3, 2); + + assertNotNull(bd.queue); + } } diff --git a/src/test/java/io/reactivex/subjects/CompletableSubjectTest.java b/src/test/java/io/reactivex/subjects/CompletableSubjectTest.java index c2c13fd5d3..56be25b2b3 100644 --- a/src/test/java/io/reactivex/subjects/CompletableSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/CompletableSubjectTest.java @@ -24,7 +24,6 @@ import io.reactivex.disposables.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class CompletableSubjectTest { @@ -125,11 +124,14 @@ public void complete() { public void nullThrowable() { CompletableSubject cs = CompletableSubject.create(); - TestObserver<Void> to = cs.test(); - - cs.onError(null); + try { + cs.onError(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onError called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } - to.assertFailure(NullPointerException.class); + cs.test().assertEmpty().cancel();; } @Test @@ -202,7 +204,7 @@ public void onSubscribeDispose() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final CompletableSubject cs = CompletableSubject.create(); final TestObserver<Void> to = cs.test(); @@ -220,7 +222,7 @@ public void run() { to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/subjects/MaybeSubjectTest.java b/src/test/java/io/reactivex/subjects/MaybeSubjectTest.java index 8d40960d19..b2a62c894b 100644 --- a/src/test/java/io/reactivex/subjects/MaybeSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/MaybeSubjectTest.java @@ -24,7 +24,6 @@ import io.reactivex.disposables.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class MaybeSubjectTest { @@ -176,28 +175,6 @@ public void complete() { assertEquals(0, ms.observerCount()); } - @Test - public void nullValue() { - MaybeSubject<Integer> ms = MaybeSubject.create(); - - TestObserver<Integer> to = ms.test(); - - ms.onSuccess(null); - - to.assertFailure(NullPointerException.class); - } - - @Test - public void nullThrowable() { - MaybeSubject<Integer> ms = MaybeSubject.create(); - - TestObserver<Integer> to = ms.test(); - - ms.onError(null); - - to.assertFailure(NullPointerException.class); - } - @Test public void cancelOnArrival() { MaybeSubject.create() @@ -273,7 +250,7 @@ public void onSubscribeDispose() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final MaybeSubject<Integer> ms = MaybeSubject.create(); final TestObserver<Integer> to = ms.test(); @@ -291,7 +268,7 @@ public void run() { to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/subjects/PublishSubjectTest.java b/src/test/java/io/reactivex/subjects/PublishSubjectTest.java index c7b6d2810d..fc20c7b195 100644 --- a/src/test/java/io/reactivex/subjects/PublishSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/PublishSubjectTest.java @@ -28,9 +28,13 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.*; -import io.reactivex.schedulers.Schedulers; -public class PublishSubjectTest { +public class PublishSubjectTest extends SubjectTest<Integer> { + + @Override + protected Subject<Integer> create() { + return PublishSubject.create(); + } @Test public void testCompleted() { @@ -62,9 +66,9 @@ public void testCompletedStopsEmittingData() { Observer<Object> observerB = TestHelper.mockObserver(); Observer<Object> observerC = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(observerA); + TestObserver<Object> to = new TestObserver<Object>(observerA); - channel.subscribe(ts); + channel.subscribe(to); channel.subscribe(observerB); InOrder inOrderA = inOrder(observerA); @@ -76,7 +80,7 @@ public void testCompletedStopsEmittingData() { inOrderA.verify(observerA).onNext(42); inOrderB.verify(observerB).onNext(42); - ts.dispose(); + to.dispose(); inOrderA.verifyNoMoreInteractions(); channel.onNext(4711); @@ -171,13 +175,13 @@ public void testUnsubscribeFirstSubscriber() { PublishSubject<String> subject = PublishSubject.create(); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - subject.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + subject.subscribe(to); subject.onNext("one"); subject.onNext("two"); - ts.dispose(); + to.dispose(); assertObservedUntilTwo(observer); Observer<String> anotherSubscriber = TestHelper.mockObserver(); @@ -257,8 +261,8 @@ public void testReSubscribe() { final PublishSubject<Integer> ps = PublishSubject.create(); Observer<Integer> o1 = TestHelper.mockObserver(); - TestObserver<Integer> ts = new TestObserver<Integer>(o1); - ps.subscribe(ts); + TestObserver<Integer> to = new TestObserver<Integer>(o1); + ps.subscribe(to); // emit ps.onNext(1); @@ -269,14 +273,14 @@ public void testReSubscribe() { inOrder1.verifyNoMoreInteractions(); // unsubscribe - ts.dispose(); + to.dispose(); // emit again but nothing will be there to receive it ps.onNext(2); Observer<Integer> o2 = TestHelper.mockObserver(); - TestObserver<Integer> ts2 = new TestObserver<Integer>(o2); - ps.subscribe(ts2); + TestObserver<Integer> to2 = new TestObserver<Integer>(o2); + ps.subscribe(to2); // emit ps.onNext(3); @@ -286,7 +290,7 @@ public void testReSubscribe() { inOrder2.verify(o2, times(1)).onNext(3); inOrder2.verifyNoMoreInteractions(); - ts2.dispose(); + to2.dispose(); } private final Throwable testException = new Throwable(); @@ -333,15 +337,14 @@ public void onComplete() { } } - // FIXME RS subscribers are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { // PublishSubject<String> ps = PublishSubject.create(); // // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // // try { // ps.onError(new RuntimeException("an exception")); @@ -350,7 +353,7 @@ public void onComplete() { // // ignore // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } // FIXME RS subscribers are not allowed to throw @@ -363,8 +366,8 @@ public void onComplete() { // // ps.subscribe(); // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // ps.subscribe(); // ps.subscribe(); // ps.subscribe(); @@ -377,8 +380,9 @@ public void onComplete() { // assertEquals(5, e.getExceptions().size()); // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } + @Test public void testCurrentStateMethodsNormal() { PublishSubject<Object> as = PublishSubject.create(); @@ -414,6 +418,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { PublishSubject<Object> as = PublishSubject.create(); @@ -437,83 +442,83 @@ public void requestValidation() { @Test public void crossCancel() { - final TestObserver<Integer> ts1 = new TestObserver<Integer>(); - TestObserver<Integer> ts2 = new TestObserver<Integer>() { + final TestObserver<Integer> to1 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); - ts1.cancel(); + to1.cancel(); } }; - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - pp.subscribe(ts2); - pp.subscribe(ts1); + ps.subscribe(to2); + ps.subscribe(to1); - pp.onNext(1); + ps.onNext(1); - ts2.assertValue(1); + to2.assertValue(1); - ts1.assertNoValues(); + to1.assertNoValues(); } @Test public void crossCancelOnError() { - final TestObserver<Integer> ts1 = new TestObserver<Integer>(); - TestObserver<Integer> ts2 = new TestObserver<Integer>() { + final TestObserver<Integer> to1 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>() { @Override public void onError(Throwable t) { super.onError(t); - ts1.cancel(); + to1.cancel(); } }; - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - pp.subscribe(ts2); - pp.subscribe(ts1); + ps.subscribe(to2); + ps.subscribe(to1); - pp.onError(new TestException()); + ps.onError(new TestException()); - ts2.assertError(TestException.class); + to2.assertError(TestException.class); - ts1.assertNoErrors(); + to1.assertNoErrors(); } @Test public void crossCancelOnComplete() { - final TestObserver<Integer> ts1 = new TestObserver<Integer>(); - TestObserver<Integer> ts2 = new TestObserver<Integer>() { + final TestObserver<Integer> to1 = new TestObserver<Integer>(); + TestObserver<Integer> to2 = new TestObserver<Integer>() { @Override public void onComplete() { super.onComplete(); - ts1.cancel(); + to1.cancel(); } }; - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - pp.subscribe(ts2); - pp.subscribe(ts1); + ps.subscribe(to2); + ps.subscribe(to1); - pp.onComplete(); + ps.onComplete(); - ts2.assertComplete(); + to2.assertComplete(); - ts1.assertNotComplete(); + to1.assertNotComplete(); } @Test @Ignore("Observable doesn't do backpressure") public void backpressureOverflow() { -// PublishSubject<Integer> pp = PublishSubject.create(); +// PublishSubject<Integer> ps = PublishSubject.create(); // -// TestObserver<Integer> ts = pp.test(0L); +// TestObserver<Integer> to = ps.test(0L); // -// pp.onNext(1); +// ps.onNext(1); // -// ts.assertNoValues() +// to.assertNoValues() // .assertNotComplete() // .assertError(MissingBackpressureException.class) // ; @@ -521,16 +526,16 @@ public void backpressureOverflow() { @Test public void onSubscribeCancelsImmediately() { - PublishSubject<Integer> pp = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.test(); + TestObserver<Integer> to = ps.test(); - pp.subscribe(new Observer<Integer>() { + ps.subscribe(new Observer<Integer>() { @Override - public void onSubscribe(Disposable s) { - s.dispose(); - s.dispose(); + public void onSubscribe(Disposable d) { + d.dispose(); + d.dispose(); } @Override @@ -550,29 +555,29 @@ public void onComplete() { }); - ts.cancel(); + to.cancel(); - assertFalse(pp.hasObservers()); + assertFalse(ps.hasObservers()); } @Test public void terminateRace() throws Exception { - for (int i = 0; i < 100; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); - TestObserver<Integer> ts = pp.test(); + TestObserver<Integer> to = ps.test(); Runnable task = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; - TestHelper.race(task, task, Schedulers.io()); + TestHelper.race(task, task); - ts + to .awaitDone(5, TimeUnit.SECONDS) .assertResult(); } @@ -581,153 +586,105 @@ public void run() { @Test public void addRemoveRance() throws Exception { - for (int i = 0; i < 100; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); - final TestObserver<Integer> ts = pp.test(); + final TestObserver<Integer> to = ps.test(); Runnable r1 = new Runnable() { @Override public void run() { - pp.subscribe(); + ps.subscribe(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.io()); + TestHelper.race(r1, r2); } } @Test public void addTerminateRance() throws Exception { - for (int i = 0; i < 100; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); Runnable r1 = new Runnable() { @Override public void run() { - pp.subscribe(); + ps.subscribe(); } }; Runnable r2 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; - TestHelper.race(r1, r2, Schedulers.io()); + TestHelper.race(r1, r2); } } @Test public void addCompleteRance() throws Exception { - for (int i = 0; i < 100; i++) { - final PublishSubject<Integer> pp = PublishSubject.create(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishSubject<Integer> ps = PublishSubject.create(); - final TestObserver<Integer> ts = new TestObserver<Integer>(); + final TestObserver<Integer> to = new TestObserver<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { - pp.subscribe(ts); + ps.subscribe(to); } }; Runnable r2 = new Runnable() { @Override public void run() { - pp.onComplete(); + ps.onComplete(); } }; - TestHelper.race(r1, r2, Schedulers.io()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS) + to.awaitDone(5, TimeUnit.SECONDS) .assertResult(); } } @Test public void subscribeToAfterComplete() { - PublishSubject<Integer> pp = PublishSubject.create(); - - pp.onComplete(); - - PublishSubject<Integer> pp2 = PublishSubject.create(); + PublishSubject<Integer> ps = PublishSubject.create(); - pp2.subscribe(pp); + ps.onComplete(); - assertFalse(pp2.hasObservers()); - } - - @Test - public void nullOnNext() { - PublishSubject<Integer> pp = PublishSubject.create(); - - TestObserver<Integer> ts = pp.test(); + PublishSubject<Integer> ps2 = PublishSubject.create(); - assertTrue(pp.hasObservers()); + ps2.subscribe(ps); - pp.onNext(null); - - ts.assertFailure(NullPointerException.class); - } - - @Test - public void nullOnError() { - PublishSubject<Integer> pp = PublishSubject.create(); - - TestObserver<Integer> ts = pp.test(); - - pp.onError(null); - - ts.assertFailure(NullPointerException.class); + assertFalse(ps2.hasObservers()); } @Test public void subscribedTo() { - PublishSubject<Integer> pp = PublishSubject.create(); - PublishSubject<Integer> pp2 = PublishSubject.create(); - - pp.subscribe(pp2); + PublishSubject<Integer> ps = PublishSubject.create(); + PublishSubject<Integer> ps2 = PublishSubject.create(); - TestObserver<Integer> ts = pp2.test(); + ps.subscribe(ps2); - pp.onNext(1); - pp.onNext(2); - pp.onComplete(); - - ts.assertResult(1, 2); - } - - @Test - public void onNextNull() { - final PublishSubject<Object> s = PublishSubject.create(); + TestObserver<Integer> to = ps2.test(); - s.onNext(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNull() { - final PublishSubject<Object> s = PublishSubject.create(); - - s.onError(null); + ps.onNext(1); + ps.onNext(2); + ps.onComplete(); - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + to.assertResult(1, 2); } } diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java index e93b4860da..693fd9a6ee 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectBoundedConcurrencyTest.java @@ -288,17 +288,18 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test public void testRaceForTerminalState() { final List<Integer> expected = Arrays.asList(1); for (int i = 0; i < 100000; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).subscribeOn(Schedulers.computation()).cache().subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertValueSequence(expected); - ts.assertTerminated(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).subscribeOn(Schedulers.computation()).cache().subscribe(to); + to.awaitTerminalEvent(); + to.assertValueSequence(expected); + to.assertTerminated(); } } @@ -322,6 +323,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -407,6 +409,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -461,6 +464,7 @@ public void run() { t.join(); } + @Test(timeout = 5000) public void testConcurrentSizeAndHasAnyValueBounded() throws InterruptedException { final ReplaySubject<Object> rs = ReplaySubject.createWithSize(3); @@ -504,6 +508,7 @@ public void run() { t.join(); } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValueTimeBounded() throws InterruptedException { final ReplaySubject<Object> rs = ReplaySubject.createWithTime(1, TimeUnit.MILLISECONDS, Schedulers.computation()); diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java index 7275df7cd0..67120c49bc 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectConcurrencyTest.java @@ -288,17 +288,18 @@ public void run() { } /** + * Make sure emission-subscription races are handled correctly. * https://github.com/ReactiveX/RxJava/issues/1147 */ @Test public void testRaceForTerminalState() { final List<Integer> expected = Arrays.asList(1); for (int i = 0; i < 100000; i++) { - TestObserver<Integer> ts = new TestObserver<Integer>(); - Observable.just(1).subscribeOn(Schedulers.computation()).cache().subscribe(ts); - ts.awaitTerminalEvent(); - ts.assertValueSequence(expected); - ts.assertTerminated(); + TestObserver<Integer> to = new TestObserver<Integer>(); + Observable.just(1).subscribeOn(Schedulers.computation()).cache().subscribe(to); + to.awaitTerminalEvent(); + to.assertValueSequence(expected); + to.assertTerminated(); } } @@ -322,6 +323,7 @@ public void run() { } } } + @Test public void testReplaySubjectEmissionSubscriptionRace() throws Exception { Scheduler s = Schedulers.io(); @@ -395,6 +397,7 @@ public void run() { worker.dispose(); } } + @Test(timeout = 10000) public void testConcurrentSizeAndHasAnyValue() throws InterruptedException { final ReplaySubject<Object> rs = ReplaySubject.create(); diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java index 0f5e1032ab..ce098ca16d 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java @@ -14,27 +14,33 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.lang.management.*; import java.util.Arrays; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.*; import io.reactivex.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.functions.Function; +import io.reactivex.functions.*; import io.reactivex.observers.*; import io.reactivex.schedulers.*; +import io.reactivex.subjects.ReplaySubject.*; -public class ReplaySubjectTest { +public class ReplaySubjectTest extends SubjectTest<Integer> { private final Throwable testException = new Throwable(); + @Override + protected Subject<Integer> create() { + return ReplaySubject.create(); + } + @Test public void testCompleted() { ReplaySubject<String> subject = ReplaySubject.create(); @@ -66,9 +72,9 @@ public void testCompletedStopsEmittingData() { Observer<Object> observerB = TestHelper.mockObserver(); Observer<Object> observerC = TestHelper.mockObserver(); Observer<Object> observerD = TestHelper.mockObserver(); - TestObserver<Object> ts = new TestObserver<Object>(observerA); + TestObserver<Object> to = new TestObserver<Object>(observerA); - channel.subscribe(ts); + channel.subscribe(to); channel.subscribe(observerB); InOrder inOrderA = inOrder(observerA); @@ -82,7 +88,7 @@ public void testCompletedStopsEmittingData() { inOrderA.verify(observerA).onNext(42); inOrderB.verify(observerB).onNext(42); - ts.dispose(); + to.dispose(); // a should receive no more inOrderA.verifyNoMoreInteractions(); @@ -217,13 +223,13 @@ public void testUnsubscribeFirstSubscriber() { ReplaySubject<String> subject = ReplaySubject.create(); Observer<String> observer = TestHelper.mockObserver(); - TestObserver<String> ts = new TestObserver<String>(observer); - subject.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(observer); + subject.subscribe(to); subject.onNext("one"); subject.onNext("two"); - ts.dispose(); + to.dispose(); assertObservedUntilTwo(observer); Observer<String> anotherSubscriber = TestHelper.mockObserver(); @@ -342,18 +348,20 @@ public void onNext(String v) { assertEquals("three", lastValueForSubscriber2.get()); } + @Test public void testSubscriptionLeak() { ReplaySubject<Object> subject = ReplaySubject.create(); - Disposable s = subject.subscribe(); + Disposable d = subject.subscribe(); assertEquals(1, subject.observerCount()); - s.dispose(); + d.dispose(); assertEquals(0, subject.observerCount()); } + @Test(timeout = 1000) public void testUnsubscriptionCase() { ReplaySubject<String> src = ReplaySubject.create(); @@ -395,6 +403,7 @@ public void onComplete() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testTerminateOnce() { ReplaySubject<Integer> source = ReplaySubject.create(); @@ -447,6 +456,7 @@ public void testReplay1AfterTermination() { verify(o, never()).onError(any(Throwable.class)); } } + @Test public void testReplay1Directly() { ReplaySubject<Integer> source = ReplaySubject.createWithSize(1); @@ -535,8 +545,8 @@ public void testReplayTimestampedDirectly() { // ReplaySubject<String> ps = ReplaySubject.create(); // // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // // try { // ps.onError(new RuntimeException("an exception")); @@ -545,7 +555,7 @@ public void testReplayTimestampedDirectly() { // // ignore // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.errors().size()); +// assertEquals(1, to.errors().size()); // } // FIXME RS subscribers can't throw @@ -558,8 +568,8 @@ public void testReplayTimestampedDirectly() { // // ps.subscribe(); // ps.subscribe(); -// TestObserver<String> ts = new TestObserver<String>(); -// ps.subscribe(ts); +// TestObserver<String> to = new TestObserver<String>(); +// ps.subscribe(to); // ps.subscribe(); // ps.subscribe(); // ps.subscribe(); @@ -572,7 +582,7 @@ public void testReplayTimestampedDirectly() { // assertEquals(5, e.getExceptions().size()); // } // // even though the onError above throws we should still receive it on the other subscriber -// assertEquals(1, ts.getOnErrorEvents().size()); +// assertEquals(1, to.getOnErrorEvents().size()); // } @Test @@ -610,6 +620,7 @@ public void testCurrentStateMethodsEmpty() { assertTrue(as.hasComplete()); assertNull(as.getThrowable()); } + @Test public void testCurrentStateMethodsError() { ReplaySubject<Object> as = ReplaySubject.create(); @@ -624,6 +635,7 @@ public void testCurrentStateMethodsError() { assertFalse(as.hasComplete()); assertTrue(as.getThrowable() instanceof TestException); } + @Test public void testSizeAndHasAnyValueUnbounded() { ReplaySubject<Object> rs = ReplaySubject.create(); @@ -646,6 +658,7 @@ public void testSizeAndHasAnyValueUnbounded() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnbounded() { ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -691,6 +704,7 @@ public void testSizeAndHasAnyValueUnboundedError() { assertEquals(2, rs.size()); assertTrue(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedError() { ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -723,6 +737,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyError() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyError() { ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -742,6 +757,7 @@ public void testSizeAndHasAnyValueUnboundedEmptyCompleted() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testSizeAndHasAnyValueEffectivelyUnboundedEmptyCompleted() { ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -774,8 +790,8 @@ public void testSizeAndHasAnyValueSizeBounded() { @Test public void testSizeAndHasAnyValueTimeBounded() { - TestScheduler ts = new TestScheduler(); - ReplaySubject<Object> rs = ReplaySubject.createWithTime(1, TimeUnit.SECONDS, ts); + TestScheduler to = new TestScheduler(); + ReplaySubject<Object> rs = ReplaySubject.createWithTime(1, TimeUnit.SECONDS, to); assertEquals(0, rs.size()); assertFalse(rs.hasValue()); @@ -784,7 +800,7 @@ public void testSizeAndHasAnyValueTimeBounded() { rs.onNext(i); assertEquals(1, rs.size()); assertTrue(rs.hasValue()); - ts.advanceTimeBy(2, TimeUnit.SECONDS); + to.advanceTimeBy(2, TimeUnit.SECONDS); assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } @@ -794,6 +810,7 @@ public void testSizeAndHasAnyValueTimeBounded() { assertEquals(0, rs.size()); assertFalse(rs.hasValue()); } + @Test public void testGetValues() { ReplaySubject<Object> rs = ReplaySubject.create(); @@ -808,6 +825,7 @@ public void testGetValues() { assertArrayEquals(expected, rs.getValues()); } + @Test public void testGetValuesUnbounded() { ReplaySubject<Object> rs = ReplaySubject.createUnbounded(); @@ -858,11 +876,11 @@ public void hasSubscribers() { assertFalse(rp.hasObservers()); - TestObserver<Integer> ts = rp.test(); + TestObserver<Integer> to = rp.test(); assertTrue(rp.hasObservers()); - ts.cancel(); + to.cancel(); assertFalse(rp.hasObservers()); } @@ -946,35 +964,11 @@ public void peekStateTimeAndSizeValueExpired() { scheduler.advanceTimeBy(2, TimeUnit.DAYS); - assertEquals(null, rp.getValue()); + assertNull(rp.getValue()); assertEquals(0, rp.getValues().length); assertNull(rp.getValues(new Integer[2])[0]); } - @Test - public void onNextNull() { - final ReplaySubject<Object> s = ReplaySubject.create(); - - s.onNext(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNull() { - final ReplaySubject<Object> s = ReplaySubject.create(); - - s.onError(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - @Test public void capacityHint() { ReplaySubject<Integer> rp = ReplaySubject.create(8); @@ -989,26 +983,26 @@ public void capacityHint() { @Test public void subscribeCancelRace() { - for (int i = 0; i < 500; i++) { - final TestObserver<Integer> ts = new TestObserver<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final TestObserver<Integer> to = new TestObserver<Integer>(); final ReplaySubject<Integer> rp = ReplaySubject.create(); Runnable r1 = new Runnable() { @Override public void run() { - rp.subscribe(ts); + rp.subscribe(to); } }; Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -1026,7 +1020,7 @@ public void subscribeAfterDone() { @Test public void subscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplaySubject<Integer> rp = ReplaySubject.create(); Runnable r1 = new Runnable() { @@ -1036,7 +1030,7 @@ public void run() { } }; - TestHelper.race(r1, r1, Schedulers.single()); + TestHelper.race(r1, r1); } } @@ -1046,36 +1040,36 @@ public void cancelUpfront() { rp.test(); rp.test(); - TestObserver<Integer> ts = rp.test(true); + TestObserver<Integer> to = rp.test(true); assertEquals(2, rp.observerCount()); - ts.assertEmpty(); + to.assertEmpty(); } @Test public void cancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final ReplaySubject<Integer> rp = ReplaySubject.create(); - final TestObserver<Integer> ts1 = rp.test(); - final TestObserver<Integer> ts2 = rp.test(); + final TestObserver<Integer> to1 = rp.test(); + final TestObserver<Integer> to2 = rp.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts1.cancel(); + to1.cancel(); } }; Runnable r2 = new Runnable() { @Override public void run() { - ts2.cancel(); + to2.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertFalse(rp.hasObservers()); } @@ -1158,7 +1152,7 @@ public void reentrantDrain() { final ReplaySubject<Integer> rp = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, scheduler, 2); - TestObserver<Integer> ts = new TestObserver<Integer>() { + TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { if (t == 1) { @@ -1168,12 +1162,12 @@ public void onNext(Integer t) { } }; - rp.subscribe(ts); + rp.subscribe(to); rp.onNext(1); rp.onComplete(); - ts.assertResult(1, 2); + to.assertResult(1, 2); } @Test @@ -1203,4 +1197,224 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void noHeadRetentionCompleteSize() { + ReplaySubject<Integer> source = ReplaySubject.createWithSize(1); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + SizeBoundReplayBuffer<Integer> buf = (SizeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionSize() { + ReplaySubject<Integer> source = ReplaySubject.createWithSize(1); + + source.onNext(1); + source.onNext(2); + + SizeBoundReplayBuffer<Integer> buf = (SizeBoundReplayBuffer<Integer>)source.buffer; + + assertNotNull(buf.head.value); + + source.cleanupBuffer(); + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionCompleteTime() { + ReplaySubject<Integer> source = ReplaySubject.createWithTime(1, TimeUnit.MINUTES, Schedulers.computation()); + + source.onNext(1); + source.onNext(2); + source.onComplete(); + + SizeAndTimeBoundReplayBuffer<Integer> buf = (SizeAndTimeBoundReplayBuffer<Integer>)source.buffer; + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noHeadRetentionTime() { + TestScheduler sch = new TestScheduler(); + + ReplaySubject<Integer> source = ReplaySubject.createWithTime(1, TimeUnit.MILLISECONDS, sch); + + source.onNext(1); + + sch.advanceTimeBy(2, TimeUnit.MILLISECONDS); + + source.onNext(2); + + SizeAndTimeBoundReplayBuffer<Integer> buf = (SizeAndTimeBoundReplayBuffer<Integer>)source.buffer; + + assertNotNull(buf.head.value); + + source.cleanupBuffer(); + + assertNull(buf.head.value); + + Object o = buf.head; + + source.cleanupBuffer(); + + assertSame(o, buf.head); + } + + @Test + public void noBoundedRetentionViaThreadLocal() throws Exception { + final ReplaySubject<byte[]> rs = ReplaySubject.createWithSize(1); + + Observable<byte[]> source = rs.take(1) + .concatMap(new Function<byte[], Observable<byte[]>>() { + @Override + public Observable<byte[]> apply(byte[] v) throws Exception { + return rs; + } + }) + .takeLast(1) + ; + + System.out.println("Bounded Replay Leak check: Wait before GC"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC"); + System.gc(); + + Thread.sleep(500); + + final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); + long initial = memHeap.getUsed(); + + System.out.printf("Bounded Replay Leak check: Starting: %.3f MB%n", initial / 1024.0 / 1024.0); + + final AtomicLong after = new AtomicLong(); + + source.subscribe(new Consumer<byte[]>() { + @Override + public void accept(byte[] v) throws Exception { + System.out.println("Bounded Replay Leak check: Wait before GC 2"); + Thread.sleep(1000); + + System.out.println("Bounded Replay Leak check: GC 2"); + System.gc(); + + Thread.sleep(500); + + after.set(memoryMXBean.getHeapMemoryUsage().getUsed()); + } + }); + + for (int i = 0; i < 200; i++) { + rs.onNext(new byte[1024 * 1024]); + } + rs.onComplete(); + + System.out.printf("Bounded Replay Leak check: After: %.3f MB%n", after.get() / 1024.0 / 1024.0); + + if (initial + 100 * 1024 * 1024 < after.get()) { + Assert.fail("Bounded Replay Leak check: Memory leak detected: " + (initial / 1024.0 / 1024.0) + + " -> " + after.get() / 1024.0 / 1024.0); + } + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange() { + ReplaySubject<Integer> rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver<Integer> to = rs.test(); + + rs.onNext(1); + rs.cleanupBuffer(); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange2() { + ReplaySubject<Integer> rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver<Integer> to = rs.test(); + + rs.onNext(1); + rs.cleanupBuffer(); + rs.onNext(2); + rs.cleanupBuffer(); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange3() { + ReplaySubject<Integer> rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver<Integer> to = rs.test(); + + rs.onNext(1); + rs.onNext(2); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange4() { + ReplaySubject<Integer> rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 10); + + TestObserver<Integer> to = rs.test(); + + rs.onNext(1); + rs.onNext(2); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeRemoveCorrectNumberOfOld() { + TestScheduler scheduler = new TestScheduler(); + ReplaySubject<Integer> rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, scheduler, 2); + + rs.onNext(1); + rs.onNext(2); + rs.onNext(3); // remove 1 due to maxSize, size == 2 + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + rs.onNext(4); // remove 2 due to maxSize, remove 3 due to age, size == 1 + rs.onNext(5); // size == 2 + + rs.test().assertValuesOnly(4, 5); + } } diff --git a/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java b/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java index 7328c494ca..8d679b7024 100644 --- a/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/SerializedSubjectTest.java @@ -23,23 +23,23 @@ import io.reactivex.TestHelper; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; -import io.reactivex.observers.TestObserver; +import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class SerializedSubjectTest { @Test public void testBasic() { SerializedSubject<String> subject = new SerializedSubject<String>(PublishSubject.<String> create()); - TestObserver<String> ts = new TestObserver<String>(); - subject.subscribe(ts); + TestObserver<String> to = new TestObserver<String>(); + subject.subscribe(to); subject.onNext("hello"); subject.onComplete(); - ts.awaitTerminalEvent(); - ts.assertValue("hello"); + to.awaitTerminalEvent(); + to.assertValue("hello"); } + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueRelay() { AsyncSubject<Integer> async = AsyncSubject.create(); @@ -58,6 +58,8 @@ public void testAsyncSubjectValueRelay() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueEmpty() { AsyncSubject<Integer> async = AsyncSubject.create(); @@ -75,6 +77,8 @@ public void testAsyncSubjectValueEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testAsyncSubjectValueError() { AsyncSubject<Integer> async = AsyncSubject.create(); @@ -93,6 +97,7 @@ public void testAsyncSubjectValueError() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testPublishSubjectValueRelay() { PublishSubject<Integer> async = PublishSubject.create(); @@ -117,6 +122,7 @@ public void testPublishSubjectValueEmpty() { assertFalse(serial.hasThrowable()); assertNull(serial.getThrowable()); } + @Test public void testPublishSubjectValueError() { PublishSubject<Integer> async = PublishSubject.create(); @@ -130,6 +136,7 @@ public void testPublishSubjectValueError() { assertSame(te, serial.getThrowable()); } + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectValueRelay() { BehaviorSubject<Integer> async = BehaviorSubject.create(); @@ -148,6 +155,8 @@ public void testBehaviorSubjectValueRelay() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectValueRelayIncomplete() { BehaviorSubject<Integer> async = BehaviorSubject.create(); @@ -165,6 +174,8 @@ public void testBehaviorSubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectIncompleteEmpty() { BehaviorSubject<Integer> async = BehaviorSubject.create(); @@ -181,6 +192,8 @@ public void testBehaviorSubjectIncompleteEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectEmpty() { BehaviorSubject<Integer> async = BehaviorSubject.create(); @@ -198,6 +211,8 @@ public void testBehaviorSubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + + @SuppressWarnings("deprecation") @Test public void testBehaviorSubjectError() { BehaviorSubject<Integer> async = BehaviorSubject.create(); @@ -235,6 +250,7 @@ public void testReplaySubjectValueRelay() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayIncomplete() { ReplaySubject<Integer> async = ReplaySubject.create(); @@ -252,6 +268,7 @@ public void testReplaySubjectValueRelayIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBounded() { ReplaySubject<Integer> async = ReplaySubject.createWithSize(1); @@ -271,6 +288,7 @@ public void testReplaySubjectValueRelayBounded() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedIncomplete() { ReplaySubject<Integer> async = ReplaySubject.createWithSize(1); @@ -289,6 +307,7 @@ public void testReplaySubjectValueRelayBoundedIncomplete() { assertArrayEquals(new Integer[] { 1 }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { 1, null }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { ReplaySubject<Integer> async = ReplaySubject.createWithSize(1); @@ -305,6 +324,7 @@ public void testReplaySubjectValueRelayBoundedEmptyIncomplete() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectValueRelayEmptyIncomplete() { ReplaySubject<Integer> async = ReplaySubject.create(); @@ -339,6 +359,7 @@ public void testReplaySubjectEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectError() { ReplaySubject<Integer> async = ReplaySubject.create(); @@ -375,6 +396,7 @@ public void testReplaySubjectBoundedEmpty() { assertArrayEquals(new Integer[] { null }, async.getValues(new Integer[] { 0 })); assertArrayEquals(new Integer[] { null, 0 }, async.getValues(new Integer[] { 0, 0 })); } + @Test public void testReplaySubjectBoundedError() { ReplaySubject<Integer> async = ReplaySubject.createWithSize(1); @@ -406,11 +428,11 @@ public void testDontWrapSerializedSubjectAgain() { public void normal() { Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); Observable.range(1, 10).subscribe(s); - ts.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + to.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); assertFalse(s.hasObservers()); @@ -433,10 +455,10 @@ public void normal() { @Test public void onNextOnNextRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); Runnable r1 = new Runnable() { @Override @@ -452,19 +474,19 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertSubscribed().assertNoErrors().assertNotComplete() + to.assertSubscribed().assertNoErrors().assertNotComplete() .assertValueSet(Arrays.asList(1, 2)); } } @Test public void onNextOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); final TestException ex = new TestException(); @@ -482,22 +504,22 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertError(ex).assertNotComplete(); + to.assertError(ex).assertNotComplete(); - if (ts.valueCount() != 0) { - ts.assertValue(1); + if (to.valueCount() != 0) { + to.assertValue(1); } } } @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); Runnable r1 = new Runnable() { @Override @@ -513,22 +535,22 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertComplete().assertNoErrors(); + to.assertComplete().assertNoErrors(); - if (ts.valueCount() != 0) { - ts.assertValue(1); + if (to.valueCount() != 0) { + to.assertValue(1); } } } @Test public void onNextOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); final Disposable bs = Disposables.empty(); @@ -546,18 +568,18 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertValue(1).assertNotComplete().assertNoErrors(); + to.assertValue(1).assertNotComplete().assertNoErrors(); } } @Test public void onCompleteOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); final Disposable bs = Disposables.empty(); @@ -575,18 +597,18 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertResult(); + to.assertResult(); } } @Test public void onCompleteOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); Runnable r1 = new Runnable() { @Override @@ -602,18 +624,18 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertResult(); + to.assertResult(); } } @Test public void onErrorOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); final TestException ex = new TestException(); @@ -633,9 +655,9 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertFailure(TestException.class); + to.assertFailure(TestException.class); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { @@ -646,10 +668,10 @@ public void run() { @Test public void onSubscribeOnSubscribeRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final Subject<Integer> s = PublishSubject.<Integer>create().toSerialized(); - TestObserver<Integer> ts = s.test(); + TestObserver<Integer> to = s.test(); final Disposable bs1 = Disposables.empty(); final Disposable bs2 = Disposables.empty(); @@ -668,9 +690,9 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.assertEmpty(); + to.assertEmpty(); } } } diff --git a/src/test/java/io/reactivex/subjects/SingleSubjectTest.java b/src/test/java/io/reactivex/subjects/SingleSubjectTest.java index 1ddf762f51..cdba1fe5c9 100644 --- a/src/test/java/io/reactivex/subjects/SingleSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/SingleSubjectTest.java @@ -24,7 +24,6 @@ import io.reactivex.disposables.*; import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; public class SingleSubjectTest { @@ -131,22 +130,28 @@ public void error() { public void nullValue() { SingleSubject<Integer> ss = SingleSubject.create(); - TestObserver<Integer> to = ss.test(); - - ss.onSuccess(null); + try { + ss.onSuccess(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } - to.assertFailure(NullPointerException.class); + ss.test().assertEmpty().cancel(); } @Test public void nullThrowable() { SingleSubject<Integer> ss = SingleSubject.create(); - TestObserver<Integer> to = ss.test(); - - ss.onError(null); + try { + ss.onError(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onError called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } - to.assertFailure(NullPointerException.class); + ss.test().assertEmpty().cancel(); } @Test @@ -219,7 +224,7 @@ public void onSubscribeDispose() { @Test public void addRemoveRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final SingleSubject<Integer> ss = SingleSubject.create(); final TestObserver<Integer> to = ss.test(); @@ -237,7 +242,7 @@ public void run() { to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } } diff --git a/src/test/java/io/reactivex/subjects/SubjectTest.java b/src/test/java/io/reactivex/subjects/SubjectTest.java new file mode 100644 index 0000000000..a50a17781b --- /dev/null +++ b/src/test/java/io/reactivex/subjects/SubjectTest.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.subjects; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public abstract class SubjectTest<T> { + + protected abstract Subject<T> create(); + + @Test + public void onNextNull() { + Subject<T> p = create(); + + try { + p.onNext(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onNext called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } + + p.test().assertEmpty().cancel(); + } + + @Test + public void onErrorNull() { + Subject<T> p = create(); + + try { + p.onError(null); + fail("No NullPointerException thrown"); + } catch (NullPointerException ex) { + assertEquals("onError called with null. Null values are generally not allowed in 2.x operators and sources.", ex.getMessage()); + } + + p.test().assertEmpty().cancel(); + } +} diff --git a/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java b/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java index 1788638967..ea68601722 100644 --- a/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java @@ -14,44 +14,51 @@ package io.reactivex.subjects; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.*; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; -import static org.mockito.Mockito.mock; -public class UnicastSubjectTest { +public class UnicastSubjectTest extends SubjectTest<Integer> { + + @Override + protected Subject<Integer> create() { + return UnicastSubject.create(); + } @Test public void fusionLive() { UnicastSubject<Integer> ap = UnicastSubject.create(); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - ap.subscribe(ts); + ap.subscribe(to); - ts + to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)); + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)); - ts.assertNoValues().assertNoErrors().assertNotComplete(); + to.assertNoValues().assertNoErrors().assertNotComplete(); ap.onNext(1); - ts.assertValue(1).assertNoErrors().assertNotComplete(); + to.assertValue(1).assertNoErrors().assertNotComplete(); ap.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test @@ -60,13 +67,13 @@ public void fusionOfflie() { ap.onNext(1); ap.onComplete(); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ANY); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); - ap.subscribe(ts); + ap.subscribe(to); - ts + to .assertOf(ObserverFusion.<Integer>assertFuseable()) - .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueDisposable.ASYNC)) + .assertOf(ObserverFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC)) .assertResult(1); } @@ -75,10 +82,10 @@ public void failFast() { UnicastSubject<Integer> ap = UnicastSubject.create(false); ap.onNext(1); ap.onError(new RuntimeException()); - TestObserver<Integer> ts = TestObserver.create(); - ap.subscribe(ts); + TestObserver<Integer> to = TestObserver.create(); + ap.subscribe(to); - ts + to .assertValueCount(0) .assertError(RuntimeException.class); } @@ -89,10 +96,10 @@ public void threeArgsFactoryFailFast() { UnicastSubject<Integer> ap = UnicastSubject.create(16, noop, false); ap.onNext(1); ap.onError(new RuntimeException()); - TestObserver<Integer> ts = TestObserver.create(); - ap.subscribe(ts); + TestObserver<Integer> to = TestObserver.create(); + ap.subscribe(to); - ts + to .assertValueCount(0) .assertError(RuntimeException.class); } @@ -103,10 +110,10 @@ public void threeArgsFactoryDelayError() { UnicastSubject<Integer> ap = UnicastSubject.create(16, noop, true); ap.onNext(1); ap.onError(new RuntimeException()); - TestObserver<Integer> ts = TestObserver.create(); - ap.subscribe(ts); + TestObserver<Integer> to = TestObserver.create(); + ap.subscribe(to); - ts + to .assertValueCount(1) .assertError(RuntimeException.class); } @@ -116,10 +123,10 @@ public void fusionOfflineFailFast() { UnicastSubject<Integer> ap = UnicastSubject.create(false); ap.onNext(1); ap.onError(new RuntimeException()); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ANY); - ap.subscribe(ts); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); + ap.subscribe(to); - ts + to .assertValueCount(0) .assertError(RuntimeException.class); } @@ -131,10 +138,10 @@ public void fusionOfflineFailFastMultipleEvents() { ap.onNext(2); ap.onNext(3); ap.onComplete(); - TestObserver<Integer> ts = ObserverFusion.newTest(QueueDisposable.ANY); - ap.subscribe(ts); + TestObserver<Integer> to = ObserverFusion.newTest(QueueFuseable.ANY); + ap.subscribe(to); - ts + to .assertValueCount(3) .assertComplete(); } @@ -146,10 +153,10 @@ public void failFastMultipleEvents() { ap.onNext(2); ap.onNext(3); ap.onComplete(); - TestObserver<Integer> ts = TestObserver.create(); - ap.subscribe(ts); + TestObserver<Integer> to = TestObserver.create(); + ap.subscribe(to); - ts + to .assertValueCount(3) .assertComplete(); } @@ -164,9 +171,9 @@ public void onTerminateCalledWhenOnError() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onError(new RuntimeException("some error")); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -179,9 +186,9 @@ public void onTerminateCalledWhenOnComplete() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onComplete(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -196,9 +203,9 @@ public void onTerminateCalledWhenCanceled() { final Disposable subscribe = us.subscribe(); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); subscribe.dispose(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test(expected = NullPointerException.class) @@ -216,67 +223,9 @@ public void zeroCapacityHint() { UnicastSubject.create(0); } - @Test - public void onNextNull() { - final UnicastSubject<Object> s = UnicastSubject.create(); - - s.onNext(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNull() { - final UnicastSubject<Object> s = UnicastSubject.create(); - - s.onError(null); - - s.test() - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onNextNullDelayed() { - final UnicastSubject<Object> p = UnicastSubject.create(); - - TestObserver<Object> ts = p.test(); - - p.onNext(null); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); - } - - @Test - public void onErrorNullDelayed() { - final UnicastSubject<Object> p = UnicastSubject.create(); - - assertFalse(p.hasObservers()); - - TestObserver<Object> ts = p.test(); - - assertTrue(p.hasObservers()); - - p.onError(null); - - assertFalse(p.hasObservers()); - - ts - .assertNoValues() - .assertError(NullPointerException.class) - .assertErrorMessage("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - @Test public void completeCancelRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final int[] calls = { 0 }; final UnicastSubject<Object> up = UnicastSubject.create(100, new Runnable() { @Override @@ -285,12 +234,12 @@ public void run() { } }); - final TestObserver<Object> ts = up.test(); + final TestObserver<Object> to = up.test(); Runnable r1 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; @@ -301,7 +250,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); assertEquals(1, calls[0]); } @@ -353,11 +302,11 @@ public void onErrorStatePeeking() { public void rejectSyncFusion() { UnicastSubject<Object> p = UnicastSubject.create(); - TestObserver<Object> ts = ObserverFusion.newTest(QueueDisposable.SYNC); + TestObserver<Object> to = ObserverFusion.newTest(QueueFuseable.SYNC); - p.subscribe(ts); + p.subscribe(to); - ObserverFusion.assertFusion(ts, QueueDisposable.NONE); + ObserverFusion.assertFusion(to, QueueFuseable.NONE); } @Test @@ -371,7 +320,7 @@ public void cancelOnArrival() { public void multiSubscriber() { UnicastSubject<Object> p = UnicastSubject.create(); - TestObserver<Object> ts = p.test(); + TestObserver<Object> to = p.test(); p.test() .assertFailure(IllegalStateException.class); @@ -379,17 +328,17 @@ public void multiSubscriber() { p.onNext(1); p.onComplete(); - ts.assertResult(1); + to.assertResult(1); } @Test public void fusedDrainCancel() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final UnicastSubject<Object> p = UnicastSubject.create(); - final TestObserver<Object> ts = ObserverFusion.newTest(QueueSubscription.ANY); + final TestObserver<Object> to = ObserverFusion.newTest(QueueFuseable.ANY); - p.subscribe(ts); + p.subscribe(to); Runnable r1 = new Runnable() { @Override @@ -401,11 +350,11 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - ts.cancel(); + to.cancel(); } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); } } @@ -437,4 +386,110 @@ public void dispose() { assertTrue(d.isDisposed()); } + + @Test + public void subscribeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final UnicastSubject<Integer> us = UnicastSubject.create(); + + final TestObserver<Integer> to1 = new TestObserver<Integer>(); + final TestObserver<Integer> to2 = new TestObserver<Integer>(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + us.subscribe(to1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + us.subscribe(to2); + } + }; + + TestHelper.race(r1, r2); + + if (to1.errorCount() == 0) { + to2.assertFailure(IllegalStateException.class); + } else + if (to2.errorCount() == 0) { + to1.assertFailure(IllegalStateException.class); + } else { + fail("Neither TestObserver failed"); + } + } + } + + @Test + public void hasObservers() { + UnicastSubject<Integer> us = UnicastSubject.create(); + + assertFalse(us.hasObservers()); + + TestObserver<Integer> to = us.test(); + + assertTrue(us.hasObservers()); + + to.cancel(); + + assertFalse(us.hasObservers()); + } + + @Test + public void drainFusedFailFast() { + UnicastSubject<Integer> us = UnicastSubject.create(false); + + TestObserver<Integer> to = us.to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)); + + us.done = true; + us.drainFused(to); + + to.assertResult(); + } + + @Test + public void drainFusedFailFastEmpty() { + UnicastSubject<Integer> us = UnicastSubject.create(false); + + TestObserver<Integer> to = us.to(ObserverFusion.<Integer>test(QueueFuseable.ANY, false)); + + us.drainFused(to); + + to.assertEmpty(); + } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final UnicastSubject<Integer> us = UnicastSubject.create(); + + TestObserver<Integer> to = us + .observeOn(Schedulers.io()) + .map(Functions.<Integer>identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; us.hasObservers(); i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/subscribers/DisposableSubscriberTest.java b/src/test/java/io/reactivex/subscribers/DisposableSubscriberTest.java index 8e49bdea3d..d6ec19dd7c 100644 --- a/src/test/java/io/reactivex/subscribers/DisposableSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/DisposableSubscriberTest.java @@ -86,11 +86,11 @@ public void startOnce() { tc.onSubscribe(new BooleanSubscription()); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - tc.onSubscribe(d); + tc.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); assertEquals(1, tc.start); @@ -110,11 +110,11 @@ public void dispose() { assertTrue(tc.isDisposed()); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - tc.onSubscribe(d); + tc.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); assertEquals(0, tc.start); } diff --git a/src/test/java/io/reactivex/subscribers/ResourceSubscriberTest.java b/src/test/java/io/reactivex/subscribers/ResourceSubscriberTest.java index 1623765865..bcaf51acf1 100644 --- a/src/test/java/io/reactivex/subscribers/ResourceSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/ResourceSubscriberTest.java @@ -164,11 +164,11 @@ public void startOnce() { tc.onSubscribe(new BooleanSubscription()); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - tc.onSubscribe(d); + tc.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); assertEquals(1, tc.start); @@ -183,11 +183,11 @@ public void dispose() { TestResourceSubscriber<Integer> tc = new TestResourceSubscriber<Integer>(); tc.dispose(); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - tc.onSubscribe(d); + tc.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); assertEquals(0, tc.start); } diff --git a/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java b/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java index a57aaf6a4a..3ac4a19d7c 100644 --- a/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/SafeSubscriberTest.java @@ -117,27 +117,27 @@ public void testOnErrorAfterOnCompleted() { */ private static class TestObservable implements Publisher<String> { - Subscriber<? super String> observer; + Subscriber<? super String> subscriber; /* used to simulate subscription */ public void sendOnCompleted() { - observer.onComplete(); + subscriber.onComplete(); } /* used to simulate subscription */ public void sendOnNext(String value) { - observer.onNext(value); + subscriber.onNext(value); } /* used to simulate subscription */ public void sendOnError(Throwable e) { - observer.onError(e); + subscriber.onError(e); } @Override - public void subscribe(Subscriber<? super String> observer) { - this.observer = observer; - observer.onSubscribe(new Subscription() { + public void subscribe(Subscriber<? super String> subscriber) { + this.subscriber = subscriber; + subscriber.onSubscribe(new Subscription() { @Override public void cancel() { @@ -199,7 +199,7 @@ public void onCompleteFailure() { @Test public void onErrorFailure() { try { - OBSERVER_ONERROR_FAIL().onError(new SafeSubscriberTestException("error!")); + subscriberOnErrorFail().onError(new SafeSubscriberTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { assertTrue(e instanceof SafeSubscriberTestException); @@ -211,7 +211,7 @@ public void onErrorFailure() { @Ignore("Observers can't throw") public void onErrorFailureSafe() { try { - new SafeSubscriber<String>(OBSERVER_ONERROR_FAIL()).onError(new SafeSubscriberTestException("error!")); + new SafeSubscriber<String>(subscriberOnErrorFail()).onError(new SafeSubscriberTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); @@ -237,7 +237,7 @@ public void onErrorFailureSafe() { @Ignore("Observers can't throw") public void onErrorNotImplementedFailureSafe() { try { - new SafeSubscriber<String>(OBSERVER_ONERROR_NOTIMPLEMENTED()).onError(new SafeSubscriberTestException("error!")); + new SafeSubscriber<String>(subscriberOnErrorNotImplemented()).onError(new SafeSubscriberTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { // assertTrue(e instanceof OnErrorNotImplementedException); @@ -301,10 +301,10 @@ public void request(long n) { @Test @Ignore("Observers can't throw") public void onCompleteSuccessWithUnsubscribeFailure() { - Subscriber<String> o = OBSERVER_SUCCESS(); + Subscriber<String> subscriber = subscriberSuccess(); try { - o.onSubscribe(THROWING_DISPOSABLE); - new SafeSubscriber<String>(o).onComplete(); + subscriber.onSubscribe(THROWING_DISPOSABLE); + new SafeSubscriber<String>(subscriber).onComplete(); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); @@ -322,10 +322,10 @@ public void onCompleteSuccessWithUnsubscribeFailure() { @Ignore("Observers can't throw") public void onErrorSuccessWithUnsubscribeFailure() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); - Subscriber<String> o = OBSERVER_SUCCESS(onError); + Subscriber<String> subscriber = subscriberSuccess(onError); try { - o.onSubscribe(THROWING_DISPOSABLE); - new SafeSubscriber<String>(o).onError(new SafeSubscriberTestException("failed")); + subscriber.onSubscribe(THROWING_DISPOSABLE); + new SafeSubscriber<String>(subscriber).onError(new SafeSubscriberTestException("failed")); fail("we expect the unsubscribe failure to cause an exception to be thrown"); } catch (Exception e) { e.printStackTrace(); @@ -348,10 +348,10 @@ public void onErrorSuccessWithUnsubscribeFailure() { @Test @Ignore("Observers can't throw") public void onErrorFailureWithUnsubscribeFailure() { - Subscriber<String> o = OBSERVER_ONERROR_FAIL(); + Subscriber<String> subscriber = subscriberOnErrorFail(); try { - o.onSubscribe(THROWING_DISPOSABLE); - new SafeSubscriber<String>(o).onError(new SafeSubscriberTestException("onError failure")); + subscriber.onSubscribe(THROWING_DISPOSABLE); + new SafeSubscriber<String>(subscriber).onError(new SafeSubscriberTestException("onError failure")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); @@ -385,10 +385,10 @@ public void onErrorFailureWithUnsubscribeFailure() { @Test @Ignore("Observers can't throw") public void onErrorNotImplementedFailureWithUnsubscribeFailure() { - Subscriber<String> o = OBSERVER_ONERROR_NOTIMPLEMENTED(); + Subscriber<String> subscriber = subscriberOnErrorNotImplemented(); try { - o.onSubscribe(THROWING_DISPOSABLE); - new SafeSubscriber<String>(o).onError(new SafeSubscriberTestException("error!")); + subscriber.onSubscribe(THROWING_DISPOSABLE); + new SafeSubscriber<String>(subscriber).onError(new SafeSubscriberTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); @@ -415,7 +415,7 @@ public void onErrorNotImplementedFailureWithUnsubscribeFailure() { } } - private static Subscriber<String> OBSERVER_SUCCESS() { + private static Subscriber<String> subscriberSuccess() { return new DefaultSubscriber<String>() { @Override @@ -436,7 +436,7 @@ public void onNext(String args) { } - private static Subscriber<String> OBSERVER_SUCCESS(final AtomicReference<Throwable> onError) { + private static Subscriber<String> subscriberSuccess(final AtomicReference<Throwable> onError) { return new DefaultSubscriber<String>() { @Override @@ -499,7 +499,7 @@ public void onNext(String args) { }; } - private static Subscriber<String> OBSERVER_ONERROR_FAIL() { + private static Subscriber<String> subscriberOnErrorFail() { return new DefaultSubscriber<String>() { @Override @@ -520,7 +520,7 @@ public void onNext(String args) { }; } - private static Subscriber<String> OBSERVER_ONERROR_NOTIMPLEMENTED() { + private static Subscriber<String> subscriberOnErrorNotImplemented() { return new DefaultSubscriber<String>() { @Override @@ -579,10 +579,12 @@ public void testOnCompletedThrows() { public void onNext(Integer t) { } + @Override public void onError(Throwable e) { error.set(e); } + @Override public void onComplete() { throw new TestException(); @@ -603,16 +605,18 @@ public void testActual() { @Override public void onNext(Integer t) { } + @Override public void onError(Throwable e) { } + @Override public void onComplete() { } }; SafeSubscriber<Integer> s = new SafeSubscriber<Integer>(actual); - assertSame(actual, s.actual); + assertSame(actual, s.downstream); } @Test @@ -621,13 +625,13 @@ public void dispose() { SafeSubscriber<Integer> so = new SafeSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); ts.dispose(); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); // assertTrue(so.isDisposed()); } @@ -638,9 +642,9 @@ public void onNextAfterComplete() { SafeSubscriber<Integer> so = new SafeSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); so.onComplete(); @@ -659,9 +663,9 @@ public void onNextNull() { SafeSubscriber<Integer> so = new SafeSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); so.onNext(null); @@ -710,9 +714,9 @@ public void onNextNormal() { SafeSubscriber<Integer> so = new SafeSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); so.onNext(1); so.onComplete(); @@ -1085,7 +1089,6 @@ public void cancelCrash() { } } - @Test public void requestCancelCrash() { List<Throwable> list = TestHelper.trackPluginErrors(); diff --git a/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java b/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java index 2f3df019ce..b95f00b3fd 100644 --- a/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java +++ b/src/test/java/io/reactivex/subscribers/SafeSubscriberWithPluginTest.java @@ -171,6 +171,7 @@ public void onError(Throwable e) { safe.onError(new TestException()); } + @Test(expected = RuntimeException.class) @Ignore("Subscribers can't throw") public void testPluginExceptionWhileOnErrorThrowsAndUnsubscribeThrows() { @@ -195,6 +196,7 @@ public void onError(Throwable e) { safe.onError(new TestException()); } + @Test(expected = RuntimeException.class) @Ignore("Subscribers can't throw") public void testPluginExceptionWhenUnsubscribing2() { diff --git a/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java b/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java index 3ba6a7252e..e0524350c9 100644 --- a/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java @@ -25,21 +25,21 @@ import io.reactivex.*; import io.reactivex.exceptions.TestException; -import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; public class SerializedSubscriberTest { - Subscriber<String> observer; + Subscriber<String> subscriber; @Before public void before() { - observer = TestHelper.mockSubscriber(); + subscriber = TestHelper.mockSubscriber(); } - private Subscriber<String> serializedSubscriber(Subscriber<String> o) { - return new SerializedSubscriber<String>(o); + private Subscriber<String> serializedSubscriber(Subscriber<String> subscriber) { + return new SerializedSubscriber<String>(subscriber); } @Test @@ -47,16 +47,16 @@ public void testSingleThreadedBasic() { TestSingleThreadedPublisher onSubscribe = new TestSingleThreadedPublisher("one", "two", "three"); Flowable<String> w = Flowable.unsafeCreate(onSubscribe); - Subscriber<String> aw = serializedSubscriber(observer); + Subscriber<String> aw = serializedSubscriber(subscriber); w.subscribe(aw); onSubscribe.waitToFinish(); - verify(observer, times(1)).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onComplete(); + verify(subscriber, times(1)).onNext("one"); + verify(subscriber, times(1)).onNext("two"); + verify(subscriber, times(1)).onNext("three"); + verify(subscriber, never()).onError(any(Throwable.class)); + verify(subscriber, times(1)).onComplete(); // non-deterministic because unsubscribe happens after 'waitToFinish' releases // so commenting out for now as this is not a critical thing to test here // verify(s, times(1)).unsubscribe(); @@ -157,6 +157,7 @@ public void testMultiThreadedWithNPEinMiddle() { @Test public void runOutOfOrderConcurrencyTest() { ExecutorService tp = Executors.newFixedThreadPool(20); + List<Throwable> errors = TestHelper.trackPluginErrors(); try { TestConcurrencySubscriber tw = new TestConcurrencySubscriber(); // we need Synchronized + SafeSubscriber to handle synchronization plus life-cycle @@ -191,6 +192,10 @@ public void runOutOfOrderConcurrencyTest() { @SuppressWarnings("unused") int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior // System.out.println("Number of events executed: " + numNextEvents); + + for (int i = 0; i < errors.size(); i++) { + TestHelper.assertUndeliverable(errors, i, RuntimeException.class); + } } catch (Throwable e) { fail("Concurrency test failed: " + e.getMessage()); e.printStackTrace(); @@ -201,6 +206,8 @@ public void runOutOfOrderConcurrencyTest() { } catch (InterruptedException e) { e.printStackTrace(); } + + RxJavaPlugins.reset(); } } @@ -266,7 +273,7 @@ public void testNotificationDelay() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch running = new CountDownLatch(2); - TestSubscriber<String> to = new TestSubscriber<String>(new DefaultSubscriber<String>() { + TestSubscriber<String> ts = new TestSubscriber<String>(new DefaultSubscriber<String>() { @Override public void onComplete() { @@ -289,16 +296,16 @@ public void onNext(String t) { } }); - Subscriber<String> o = serializedSubscriber(to); + Subscriber<String> subscriber = serializedSubscriber(ts); - Future<?> f1 = tp1.submit(new OnNextThread(o, 1, onNextCount, running)); - Future<?> f2 = tp2.submit(new OnNextThread(o, 1, onNextCount, running)); + Future<?> f1 = tp1.submit(new OnNextThread(subscriber, 1, onNextCount, running)); + Future<?> f2 = tp2.submit(new OnNextThread(subscriber, 1, onNextCount, running)); running.await(); // let one of the OnNextThread actually run before proceeding firstOnNext.await(); - Thread t1 = to.lastThread(); + Thread t1 = ts.lastThread(); System.out.println("first onNext on thread: " + t1); latch.countDown(); @@ -306,16 +313,16 @@ public void onNext(String t) { waitOnThreads(f1, f2); // not completed yet - assertEquals(2, to.valueCount()); + assertEquals(2, ts.valueCount()); - Thread t2 = to.lastThread(); + Thread t2 = ts.lastThread(); System.out.println("second onNext on thread: " + t2); assertSame(t1, t2); - System.out.println(to.values()); - o.onComplete(); - System.out.println(to.values()); + System.out.println(ts.values()); + subscriber.onComplete(); + System.out.println(ts.values()); } } finally { tp1.shutdown(); @@ -347,7 +354,7 @@ public void onNext(String t) { @Test public void testThreadStarvation() throws InterruptedException { - TestSubscriber<String> to = new TestSubscriber<String>(new DefaultSubscriber<String>() { + TestSubscriber<String> ts = new TestSubscriber<String>(new DefaultSubscriber<String>() { @Override public void onComplete() { @@ -369,16 +376,16 @@ public void onNext(String t) { } }); - final Subscriber<String> o = serializedSubscriber(to); + final Subscriber<String> subscriber = serializedSubscriber(ts); AtomicInteger p1 = new AtomicInteger(); AtomicInteger p2 = new AtomicInteger(); - o.onSubscribe(new BooleanSubscription()); + subscriber.onSubscribe(new BooleanSubscription()); ResourceSubscriber<String> as1 = new ResourceSubscriber<String>() { @Override public void onNext(String t) { - o.onNext(t); + subscriber.onNext(t); } @Override @@ -395,7 +402,7 @@ public void onComplete() { ResourceSubscriber<String> as2 = new ResourceSubscriber<String>() { @Override public void onNext(String t) { - o.onNext(t); + subscriber.onNext(t); } @Override @@ -454,29 +461,29 @@ public void subscribe(Subscriber<? super String> s) { public static class OnNextThread implements Runnable { private final CountDownLatch latch; - private final Subscriber<String> observer; + private final Subscriber<String> subscriber; private final int numStringsToSend; final AtomicInteger produced; private final CountDownLatch running; - OnNextThread(Subscriber<String> observer, int numStringsToSend, CountDownLatch latch, CountDownLatch running) { - this(observer, numStringsToSend, new AtomicInteger(), latch, running); + OnNextThread(Subscriber<String> subscriber, int numStringsToSend, CountDownLatch latch, CountDownLatch running) { + this(subscriber, numStringsToSend, new AtomicInteger(), latch, running); } - OnNextThread(Subscriber<String> observer, int numStringsToSend, AtomicInteger produced) { - this(observer, numStringsToSend, produced, null, null); + OnNextThread(Subscriber<String> subscriber, int numStringsToSend, AtomicInteger produced) { + this(subscriber, numStringsToSend, produced, null, null); } - OnNextThread(Subscriber<String> observer, int numStringsToSend, AtomicInteger produced, CountDownLatch latch, CountDownLatch running) { - this.observer = observer; + OnNextThread(Subscriber<String> subscriber, int numStringsToSend, AtomicInteger produced, CountDownLatch latch, CountDownLatch running) { + this.subscriber = subscriber; this.numStringsToSend = numStringsToSend; this.produced = produced; this.latch = latch; this.running = running; } - OnNextThread(Subscriber<String> observer, int numStringsToSend) { - this(observer, numStringsToSend, new AtomicInteger()); + OnNextThread(Subscriber<String> subscriber, int numStringsToSend) { + this(subscriber, numStringsToSend, new AtomicInteger()); } @Override @@ -485,7 +492,7 @@ public void run() { running.countDown(); } for (int i = 0; i < numStringsToSend; i++) { - observer.onNext(Thread.currentThread().getId() + "-" + i); + subscriber.onNext(Thread.currentThread().getId() + "-" + i); if (latch != null) { latch.countDown(); } @@ -499,12 +506,12 @@ public void run() { */ public static class CompletionThread implements Runnable { - private final Subscriber<String> observer; + private final Subscriber<String> subscriber; private final TestConcurrencySubscriberEvent event; private final Future<?>[] waitOnThese; CompletionThread(Subscriber<String> Subscriber, TestConcurrencySubscriberEvent event, Future<?>... waitOnThese) { - this.observer = Subscriber; + this.subscriber = Subscriber; this.event = event; this.waitOnThese = waitOnThese; } @@ -524,9 +531,9 @@ public void run() { /* send the event */ if (event == TestConcurrencySubscriberEvent.onError) { - observer.onError(new RuntimeException("mocked exception")); + subscriber.onError(new RuntimeException("mocked exception")); } else if (event == TestConcurrencySubscriberEvent.onComplete) { - observer.onComplete(); + subscriber.onComplete(); } else { throw new IllegalArgumentException("Expecting either onError or onComplete"); @@ -641,8 +648,8 @@ static class TestSingleThreadedPublisher implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); System.out.println("TestSingleThreadedObservable subscribed to ..."); t = new Thread(new Runnable() { @@ -652,9 +659,9 @@ public void run() { System.out.println("running TestSingleThreadedObservable thread"); for (String s : values) { System.out.println("TestSingleThreadedObservable onNext: " + s); - observer.onNext(s); + subscriber.onNext(s); } - observer.onComplete(); + subscriber.onComplete(); } catch (Throwable e) { throw new RuntimeException(e); } @@ -693,8 +700,8 @@ static class TestMultiThreadedObservable implements Publisher<String> { } @Override - public void subscribe(final Subscriber<? super String> observer) { - observer.onSubscribe(new BooleanSubscription()); + public void subscribe(final Subscriber<? super String> subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); final NullPointerException npe = new NullPointerException(); System.out.println("TestMultiThreadedObservable subscribed to ..."); t = new Thread(new Runnable() { @@ -724,7 +731,7 @@ public void run() { Thread.sleep(sleep); } } - observer.onNext(s); + subscriber.onNext(s); // capture 'maxThreads' int concurrentThreads = threadsRunning.get(); int maxThreads = maxConcurrentThreads.get(); @@ -732,7 +739,7 @@ public void run() { maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); } } catch (Throwable e) { - observer.onError(e); + subscriber.onError(e); } finally { threadsRunning.decrementAndGet(); } @@ -754,7 +761,7 @@ public void run() { } catch (InterruptedException e) { throw new RuntimeException(e); } - observer.onComplete(); + subscriber.onComplete(); } }); System.out.println("starting TestMultiThreadedObservable thread"); @@ -839,7 +846,7 @@ protected void captureMaxThreads() { @Ignore("Null values not permitted") public void testSerializeNull() { final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t != null && t == 0) { @@ -849,25 +856,25 @@ public void onNext(Integer t) { } }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(to); + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); serial.set(sobs); sobs.onNext(0); - to.assertValues(0, null); + ts.assertValues(0, null); } @Test @Ignore("Subscribers can't throw") public void testSerializeAllowsOnError() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { throw new TestException(); } }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(to); + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); try { sobs.onNext(0); @@ -875,14 +882,14 @@ public void onNext(Integer t) { sobs.onError(ex); } - to.assertError(TestException.class); + ts.assertError(TestException.class); } @Test @Ignore("Null values no longer permitted") public void testSerializeReentrantNullAndComplete() { final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { serial.get().onComplete(); @@ -890,7 +897,7 @@ public void onNext(Integer t) { } }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(to); + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); serial.set(sobs); try { @@ -899,15 +906,15 @@ public void onNext(Integer t) { sobs.onError(ex); } - to.assertError(TestException.class); - to.assertNotComplete(); + ts.assertError(TestException.class); + ts.assertNotComplete(); } @Test @Ignore("Subscribers can't throw") public void testSerializeReentrantNullAndError() { final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { serial.get().onError(new RuntimeException()); @@ -915,7 +922,7 @@ public void onNext(Integer t) { } }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(to); + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); serial.set(sobs); try { @@ -924,15 +931,15 @@ public void onNext(Integer t) { sobs.onError(ex); } - to.assertError(TestException.class); - to.assertNotComplete(); + ts.assertError(TestException.class); + ts.assertNotComplete(); } @Test @Ignore("Null values no longer permitted") public void testSerializeDrainPhaseThrows() { final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { if (t != null && t == 0) { @@ -945,36 +952,44 @@ public void onNext(Integer t) { } }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(to); + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); serial.set(sobs); sobs.onNext(0); - to.assertError(TestException.class); - to.assertNotComplete(); + ts.assertError(TestException.class); + ts.assertNotComplete(); } @Test public void testErrorReentry() { - final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { - @Override - public void onNext(Integer v) { - serial.get().onError(new TestException()); - serial.get().onError(new TestException()); - super.onNext(v); - } - }; - SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); - sobs.onSubscribe(new BooleanSubscription()); - serial.set(sobs); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { + @Override + public void onNext(Integer v) { + serial.get().onError(new TestException()); + serial.get().onError(new TestException()); + super.onNext(v); + } + }; + SerializedSubscriber<Integer> sobs = new SerializedSubscriber<Integer>(ts); + sobs.onSubscribe(new BooleanSubscription()); + serial.set(sobs); - sobs.onNext(1); + sobs.onNext(1); - ts.assertValue(1); - ts.assertError(TestException.class); + ts.assertValue(1); + ts.assertError(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } } + @Test public void testCompleteReentry() { final AtomicReference<Subscriber<Integer>> serial = new AtomicReference<Subscriber<Integer>>(); @@ -1004,25 +1019,25 @@ public void dispose() { SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); ts.cancel(); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); } @Test public void onCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); Runnable r = new Runnable() { @Override @@ -1031,7 +1046,7 @@ public void run() { } }; - TestHelper.race(r, r, Schedulers.single()); + TestHelper.race(r, r); ts.awaitDone(5, TimeUnit.SECONDS) .assertResult(); @@ -1041,14 +1056,14 @@ public void run() { @Test public void onNextOnCompleteRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); Runnable r1 = new Runnable() { @Override @@ -1064,7 +1079,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.awaitDone(5, TimeUnit.SECONDS) .assertNoErrors() @@ -1077,14 +1092,14 @@ public void run() { @Test public void onNextOnErrorRace() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); final Throwable ex = new TestException(); @@ -1102,7 +1117,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.awaitDone(5, TimeUnit.SECONDS) .assertError(ex) @@ -1115,14 +1130,14 @@ public void run() { @Test public void onNextOnErrorRaceDelayError() { - for (int i = 0; i < 500; i++) { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts, true); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); final Throwable ex = new TestException(); @@ -1140,7 +1155,7 @@ public void run() { } }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); ts.awaitDone(5, TimeUnit.SECONDS) .assertError(ex) @@ -1163,11 +1178,11 @@ public void startOnce() { so.onSubscribe(new BooleanSubscription()); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); - assertTrue(d.isCancelled()); + assertTrue(bs.isCancelled()); TestHelper.assertError(error, 0, IllegalStateException.class, "Subscription already set!"); } finally { @@ -1177,41 +1192,62 @@ public void startOnce() { @Test public void onCompleteOnErrorRace() { - for (int i = 0; i < 500; i++) { - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + List<Throwable> errors = TestHelper.trackPluginErrors(); + try { + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); - final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); + final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); - BooleanSubscription d = new BooleanSubscription(); + BooleanSubscription bs = new BooleanSubscription(); - so.onSubscribe(d); + so.onSubscribe(bs); - final Throwable ex = new TestException(); + final Throwable ex = new TestException(); - Runnable r1 = new Runnable() { - @Override - public void run() { - so.onError(ex); - } - }; + Runnable r1 = new Runnable() { + @Override + public void run() { + so.onError(ex); + } + }; - Runnable r2 = new Runnable() { - @Override - public void run() { - so.onComplete(); - } - }; + Runnable r2 = new Runnable() { + @Override + public void run() { + so.onComplete(); + } + }; - TestHelper.race(r1, r2, Schedulers.single()); + TestHelper.race(r1, r2); - ts.awaitDone(5, TimeUnit.SECONDS); + ts.awaitDone(5, TimeUnit.SECONDS); - if (ts.completions() != 0) { - ts.assertResult(); - } else { - ts.assertFailure(TestException.class).assertError(ex); + if (ts.completions() != 0) { + ts.assertResult(); + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } else { + ts.assertFailure(TestException.class).assertError(ex); + assertTrue("" + errors, errors.isEmpty()); + } + } finally { + RxJavaPlugins.reset(); } } } + + @Test + public void nullOnNext() { + + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + + final SerializedSubscriber<Integer> so = new SerializedSubscriber<Integer>(ts); + + so.onSubscribe(new BooleanSubscription()); + + so.onNext(null); + + ts.assertFailureAndMessage(NullPointerException.class, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + } } diff --git a/src/test/java/io/reactivex/subscribers/SubscriberFusion.java b/src/test/java/io/reactivex/subscribers/SubscriberFusion.java index 43e15e350b..02e1f3a9a5 100644 --- a/src/test/java/io/reactivex/subscribers/SubscriberFusion.java +++ b/src/test/java/io/reactivex/subscribers/SubscriberFusion.java @@ -32,12 +32,12 @@ public enum SubscriberFusion { * Use this as follows: * <pre> * source - * .to(SubscriberFusion.test(0, QueueSubscription.ANY, false)) + * .to(SubscriberFusion.test(0, QueueFuseable.ANY, false)) * .assertResult(0); * </pre> * @param <T> the value type * @param initialRequest the initial request amount, non-negative - * @param mode the fusion mode to request, see {@link QueueSubscription} constants. + * @param mode the fusion mode to request, see {@link QueueFuseable} constants. * @param cancelled should the TestSubscriber cancelled before the subscription even happens? * @return the new Function instance */ @@ -52,7 +52,7 @@ public static <T> Function<Flowable<T>, TestSubscriber<T>> test( * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) + * .to(ObserverFusion.test(0, QueueFuseable.ANY, false)) * .assertOf(ObserverFusion.assertFuseable()); * </pre> * @param <T> the value type @@ -114,7 +114,7 @@ public void accept(TestSubscriber<Object> ts) throws Exception { * Use this as follows: * <pre> * source - * .to(ObserverFusion.test(0, QueueDisposable.ANY, false)) + * .to(ObserverFusion.test(0, QueueFuseable.ANY, false)) * .assertOf(ObserverFusion.assertNotFuseable()); * </pre> * @param <T> the value type @@ -135,17 +135,17 @@ public void accept(TestSubscriber<Object> ts) throws Exception { /** * Returns a Consumer that asserts on its TestSubscriber parameter that - * the upstream is Fuseable (sent a QueueSubscription subclass in onSubscribe) + * the upstream is Fuseable (sent a QueueFuseable.subclass in onSubscribe) * and the established the given fusion mode. * <p> * Use this as follows: * <pre> * source - * .to(SubscriberFusion.test(0, QueueSubscription.ANY, false)) - * .assertOf(SubscriberFusion.assertFusionMode(QueueSubscription.SYNC)); + * .to(SubscriberFusion.test(0, QueueFuseable.ANY, false)) + * .assertOf(SubscriberFusion.assertFusionMode(QueueFuseable.SYNC)); * </pre> * @param <T> the value type - * @param mode the expected established fusion mode, see {@link QueueSubscription} constants. + * @param mode the expected established fusion mode, see {@link QueueFuseable} constants. * @return the new Consumer instance */ public static <T> Consumer<TestSubscriber<T>> assertFusionMode(final int mode) { @@ -156,7 +156,7 @@ public static <T> Consumer<TestSubscriber<T>> assertFusionMode(final int mode) { * Constructs a TestSubscriber with the given initial request and required fusion mode. * @param <T> the value type * @param initialRequest the initial request, non-negative - * @param mode the requested fusion mode, see {@link QueueSubscription} constants + * @param mode the requested fusion mode, see {@link QueueFuseable} constants * @return the new TestSubscriber */ public static <T> TestSubscriber<T> newTest(long initialRequest, int mode) { @@ -168,7 +168,7 @@ public static <T> TestSubscriber<T> newTest(long initialRequest, int mode) { /** * Constructs a TestSubscriber with the given required fusion mode. * @param <T> the value type - * @param mode the requested fusion mode, see {@link QueueSubscription} constants + * @param mode the requested fusion mode, see {@link QueueFuseable} constants * @return the new TestSubscriber */ public static <T> TestSubscriber<T> newTest(int mode) { @@ -178,7 +178,7 @@ public static <T> TestSubscriber<T> newTest(int mode) { } /** - * Assert that the TestSubscriber received a fuseabe QueueSubscription and + * Assert that the TestSubscriber received a fuseabe QueueFuseable.and * is in the given fusion mode. * @param <T> the value type * @param ts the TestSubscriber instance diff --git a/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java b/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java index 470f5a2308..6c634d8d91 100644 --- a/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/TestSubscriberTest.java @@ -31,7 +31,7 @@ import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.internal.subscriptions.*; import io.reactivex.observers.BaseTestConsumer; import io.reactivex.observers.BaseTestConsumer.TestWaitStrategy; @@ -46,69 +46,68 @@ public class TestSubscriberTest { @Test public void testAssert() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + oi.subscribe(ts); - o.assertValues(1, 2); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertValues(1, 2); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test public void testAssertNotMatchCount() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + oi.subscribe(ts); thrown.expect(AssertionError.class); // FIXME different message pattern // thrown.expectMessage("Number of items does not match. Provided: 1 Actual: 2"); - o.assertValues(1); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertValues(1); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test public void testAssertNotMatchValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + oi.subscribe(ts); thrown.expect(AssertionError.class); // FIXME different message pattern // thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); - - o.assertValues(1, 3); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertValues(1, 3); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test public void assertNeverAtNotMatchingValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + oi.subscribe(ts); - o.assertNever(3); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertNever(3); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test public void assertNeverAtMatchingValue() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - oi.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + oi.subscribe(ts); - o.assertValues(1, 2); + ts.assertValues(1, 2); thrown.expect(AssertionError.class); - o.assertNever(2); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertNever(2); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test @@ -146,8 +145,8 @@ public boolean test(final Integer o) throws Exception { @Test public void testAssertTerminalEventNotReceived() { PublishProcessor<Integer> p = PublishProcessor.create(); - TestSubscriber<Integer> o = new TestSubscriber<Integer>(); - p.subscribe(o); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); + p.subscribe(ts); p.onNext(1); p.onNext(2); @@ -156,35 +155,35 @@ public void testAssertTerminalEventNotReceived() { // FIXME different message pattern // thrown.expectMessage("No terminal events received."); - o.assertValues(1, 2); - o.assertValueCount(2); - o.assertTerminated(); + ts.assertValues(1, 2); + ts.assertValueCount(2); + ts.assertTerminated(); } @Test public void testWrappingMock() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2)); - Subscriber<Integer> mockObserver = TestHelper.mockSubscriber(); + Subscriber<Integer> mockSubscriber = TestHelper.mockSubscriber(); - oi.subscribe(new TestSubscriber<Integer>(mockObserver)); + oi.subscribe(new TestSubscriber<Integer>(mockSubscriber)); - InOrder inOrder = inOrder(mockObserver); - inOrder.verify(mockObserver, times(1)).onNext(1); - inOrder.verify(mockObserver, times(1)).onNext(2); - inOrder.verify(mockObserver, times(1)).onComplete(); + InOrder inOrder = inOrder(mockSubscriber); + inOrder.verify(mockSubscriber, times(1)).onNext(1); + inOrder.verify(mockSubscriber, times(1)).onNext(2); + inOrder.verify(mockSubscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @Test public void testWrappingMockWhenUnsubscribeInvolved() { Flowable<Integer> oi = Flowable.fromIterable(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)).take(2); - Subscriber<Integer> mockObserver = TestHelper.mockSubscriber(); - oi.subscribe(new TestSubscriber<Integer>(mockObserver)); + Subscriber<Integer> mockSubscriber = TestHelper.mockSubscriber(); + oi.subscribe(new TestSubscriber<Integer>(mockSubscriber)); - InOrder inOrder = inOrder(mockObserver); - inOrder.verify(mockObserver, times(1)).onNext(1); - inOrder.verify(mockObserver, times(1)).onNext(2); - inOrder.verify(mockObserver, times(1)).onComplete(); + InOrder inOrder = inOrder(mockSubscriber); + inOrder.verify(mockSubscriber, times(1)).onNext(1); + inOrder.verify(mockSubscriber, times(1)).onNext(2); + inOrder.verify(mockSubscriber, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } @@ -243,13 +242,13 @@ public void testNullDelegate3() { @Test public void testDelegate1() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>(); - to.onSubscribe(EmptySubscription.INSTANCE); + TestSubscriber<Integer> ts0 = new TestSubscriber<Integer>(); + ts0.onSubscribe(EmptySubscription.INSTANCE); - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(to); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(ts0); ts.onComplete(); - to.assertTerminated(); + ts0.assertTerminated(); } @Test @@ -624,7 +623,7 @@ public void testNoTerminalEventBut1Completed() { try { ts.assertNotTerminated(); - fail("Failed to report there were terminal event(s)!"); + throw new RuntimeException("Failed to report there were terminal event(s)!"); } catch (AssertionError ex) { // expected } @@ -638,7 +637,7 @@ public void testNoTerminalEventBut1Error() { try { ts.assertNotTerminated(); - fail("Failed to report there were terminal event(s)!"); + throw new RuntimeException("Failed to report there were terminal event(s)!"); } catch (AssertionError ex) { // expected } @@ -653,7 +652,7 @@ public void testNoTerminalEventBut1Error1Completed() { try { ts.assertNotTerminated(); - fail("Failed to report there were terminal event(s)!"); + throw new RuntimeException("Failed to report there were terminal event(s)!"); } catch (AssertionError ex) { // expected } @@ -669,7 +668,7 @@ public void testNoTerminalEventBut2Errors() { try { ts.assertNotTerminated(); - fail("Failed to report there were terminal event(s)!"); + throw new RuntimeException("Failed to report there were terminal event(s)!"); } catch (AssertionError ex) { // expected Throwable e = ex.getCause(); @@ -688,7 +687,7 @@ public void testNoValues() { try { ts.assertNoValues(); - fail("Failed to report there were values!"); + throw new RuntimeException("Failed to report there were values!"); } catch (AssertionError ex) { // expected } @@ -702,7 +701,7 @@ public void testValueCount() { try { ts.assertValueCount(3); - fail("Failed to report there were values!"); + throw new RuntimeException("Failed to report there were values!"); } catch (AssertionError ex) { // expected } @@ -710,13 +709,13 @@ public void testValueCount() { @Test(timeout = 1000) public void testOnCompletedCrashCountsDownLatch() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts0 = new TestSubscriber<Integer>() { @Override public void onComplete() { throw new TestException(); } }; - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(to); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(ts0); try { ts.onComplete(); @@ -729,13 +728,13 @@ public void onComplete() { @Test(timeout = 1000) public void testOnErrorCrashCountsDownLatch() { - TestSubscriber<Integer> to = new TestSubscriber<Integer>() { + TestSubscriber<Integer> ts0 = new TestSubscriber<Integer>() { @Override public void onError(Throwable e) { throw new TestException(); } }; - TestSubscriber<Integer> ts = new TestSubscriber<Integer>(to); + TestSubscriber<Integer> ts = new TestSubscriber<Integer>(ts0); try { ts.onError(new RuntimeException()); @@ -746,7 +745,6 @@ public void onError(Throwable e) { ts.awaitTerminalEvent(); } - @Test public void createDelegate() { TestSubscriber<Integer> ts1 = TestSubscriber.create(); @@ -929,8 +927,6 @@ public boolean test(Throwable t) { ts.assertValueCount(0); ts.assertNoValues(); - - } @Test @@ -984,22 +980,22 @@ public void assertFuseable() { } try { - ts.assertFusionMode(QueueSubscription.SYNC); + ts.assertFusionMode(QueueFuseable.SYNC); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected } ts = TestSubscriber.create(); - ts.setInitialFusionMode(QueueSubscription.ANY); + ts.setInitialFusionMode(QueueFuseable.ANY); ts.onSubscribe(new ScalarSubscription<Integer>(ts, 1)); ts.assertFuseable(); - ts.assertFusionMode(QueueSubscription.SYNC); + ts.assertFusionMode(QueueFuseable.SYNC); try { - ts.assertFusionMode(QueueSubscription.NONE); + ts.assertFusionMode(QueueFuseable.NONE); throw new RuntimeException("Should have thrown"); } catch (AssertionError ex) { // expected @@ -1195,9 +1191,9 @@ public void onNext() { @Test public void fusionModeToString() { - assertEquals("NONE", TestSubscriber.fusionModeToString(QueueSubscription.NONE)); - assertEquals("SYNC", TestSubscriber.fusionModeToString(QueueSubscription.SYNC)); - assertEquals("ASYNC", TestSubscriber.fusionModeToString(QueueSubscription.ASYNC)); + assertEquals("NONE", TestSubscriber.fusionModeToString(QueueFuseable.NONE)); + assertEquals("SYNC", TestSubscriber.fusionModeToString(QueueFuseable.SYNC)); + assertEquals("ASYNC", TestSubscriber.fusionModeToString(QueueFuseable.ASYNC)); assertEquals("Unknown(100)", TestSubscriber.fusionModeToString(100)); } @@ -1341,7 +1337,6 @@ public void assertTerminated2() { // expected } - ts = TestSubscriber.create(); ts.onSubscribe(new BooleanSubscription()); @@ -1369,22 +1364,22 @@ public void onSubscribe() { ts.onSubscribe(new BooleanSubscription()); - BooleanSubscription d1 = new BooleanSubscription(); + BooleanSubscription bs1 = new BooleanSubscription(); - ts.onSubscribe(d1); + ts.onSubscribe(bs1); - assertTrue(d1.isCancelled()); + assertTrue(bs1.isCancelled()); ts.assertError(IllegalStateException.class); ts = TestSubscriber.create(); ts.dispose(); - d1 = new BooleanSubscription(); + bs1 = new BooleanSubscription(); - ts.onSubscribe(d1); + ts.onSubscribe(bs1); - assertTrue(d1.isCancelled()); + assertTrue(bs1.isCancelled()); } @@ -1544,7 +1539,7 @@ public void completeDelegateThrows() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(new FlowableSubscriber<Integer>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @@ -1580,7 +1575,7 @@ public void errorDelegateThrows() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(new FlowableSubscriber<Integer>() { @Override - public void onSubscribe(Subscription d) { + public void onSubscribe(Subscription s) { } @@ -1611,11 +1606,10 @@ public void onComplete() { } } - @Test public void syncQueueThrows() { TestSubscriber<Object> ts = new TestSubscriber<Object>(); - ts.setInitialFusionMode(QueueSubscription.SYNC); + ts.setInitialFusionMode(QueueFuseable.SYNC); Flowable.range(1, 5) .map(new Function<Integer, Object>() { @@ -1626,14 +1620,14 @@ public void syncQueueThrows() { ts.assertSubscribed() .assertFuseable() - .assertFusionMode(QueueSubscription.SYNC) + .assertFusionMode(QueueFuseable.SYNC) .assertFailure(TestException.class); } @Test public void asyncQueueThrows() { TestSubscriber<Object> ts = new TestSubscriber<Object>(); - ts.setInitialFusionMode(QueueSubscription.ANY); + ts.setInitialFusionMode(QueueFuseable.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); @@ -1648,7 +1642,7 @@ public void asyncQueueThrows() { ts.assertSubscribed() .assertFuseable() - .assertFusionMode(QueueSubscription.ASYNC) + .assertFusionMode(QueueFuseable.ASYNC) .assertFailure(TestException.class); } @@ -1790,7 +1784,7 @@ public void withTag() { .assertResult(1) ; } - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.toString().contains("testing with item=2")); } @@ -1806,7 +1800,7 @@ public void timeoutIndicated() throws InterruptedException { try { ts.assertResult(1); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.toString().contains("timeout!")); } @@ -1820,13 +1814,12 @@ public void timeoutIndicated2() throws InterruptedException { .awaitDone(1, TimeUnit.MILLISECONDS) .assertResult(1); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.toString().contains("timeout!")); } } - @Test public void timeoutIndicated3() throws InterruptedException { TestSubscriber<Object> ts = Flowable.never() @@ -1835,7 +1828,7 @@ public void timeoutIndicated3() throws InterruptedException { try { ts.assertResult(1); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.toString().contains("timeout!")); } @@ -1848,7 +1841,7 @@ public void disposeIndicated() { try { ts.assertResult(1); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (Throwable ex) { assertTrue(ex.toString(), ex.toString().contains("disposed!")); } @@ -1930,7 +1923,7 @@ public void assertTimeout2() { .test() .awaitCount(1, TestWaitStrategy.SLEEP_1MS, 50) .assertTimeout(); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.getMessage().contains("No timeout?!")); } @@ -1951,7 +1944,7 @@ public void assertNoTimeout2() { .test() .awaitCount(1, TestWaitStrategy.SLEEP_1MS, 50) .assertNoTimeout(); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (AssertionError ex) { assertTrue(ex.toString(), ex.getMessage().contains("Timeout?!")); } @@ -1968,7 +1961,7 @@ public boolean test(Integer t) throws Exception { throw new IllegalArgumentException(); } }); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (IllegalArgumentException ex) { // expected } @@ -1985,7 +1978,7 @@ public boolean test(Integer t) throws Exception { throw new IllegalArgumentException(); } }); - fail("Should have thrown!"); + throw new RuntimeException("Should have thrown!"); } catch (IllegalArgumentException ex) { // expected } @@ -1997,4 +1990,230 @@ public void waitStrategyRuns() { ws.run(); } } + + @Test + public void assertValuesOnly() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValuesOnly(); + + ts.onNext(5); + ts.assertValuesOnly(5); + + ts.onNext(-1); + ts.assertValuesOnly(5, -1); + } + + @Test + public void assertValuesOnlyThrowsOnUnexpectedValue() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValuesOnly(); + + ts.onNext(5); + ts.assertValuesOnly(5); + + ts.onNext(-1); + + try { + ts.assertValuesOnly(5); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValuesOnlyThrowsWhenCompleted() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onComplete(); + + try { + ts.assertValuesOnly(); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValuesOnlyThrowsWhenErrored() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onError(new TestException()); + + try { + ts.assertValuesOnly(); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnly() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValueSetOnly(Collections.<Integer>emptySet()); + + ts.onNext(5); + ts.assertValueSetOnly(Collections.singleton(5)); + + ts.onNext(-1); + ts.assertValueSetOnly(new HashSet<Integer>(Arrays.asList(5, -1))); + } + + @Test + public void assertValueSetOnlyThrowsOnUnexpectedValue() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValueSetOnly(Collections.<Integer>emptySet()); + + ts.onNext(5); + ts.assertValueSetOnly(Collections.singleton(5)); + + ts.onNext(-1); + + try { + ts.assertValueSetOnly(Collections.singleton(5)); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnlyThrowsWhenCompleted() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onComplete(); + + try { + ts.assertValueSetOnly(Collections.<Integer>emptySet()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSetOnlyThrowsWhenErrored() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onError(new TestException()); + + try { + ts.assertValueSetOnly(Collections.<Integer>emptySet()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnly() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValueSequenceOnly(Collections.<Integer>emptyList()); + + ts.onNext(5); + ts.assertValueSequenceOnly(Collections.singletonList(5)); + + ts.onNext(-1); + ts.assertValueSequenceOnly(Arrays.asList(5, -1)); + } + + @Test + public void assertValueSequenceOnlyThrowsOnUnexpectedValue() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + ts.assertValueSequenceOnly(Collections.<Integer>emptyList()); + + ts.onNext(5); + ts.assertValueSequenceOnly(Collections.singletonList(5)); + + ts.onNext(-1); + + try { + ts.assertValueSequenceOnly(Collections.singletonList(5)); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnlyThrowsWhenCompleted() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onComplete(); + + try { + ts.assertValueSequenceOnly(Collections.<Integer>emptyList()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test + public void assertValueSequenceOnlyThrowsWhenErrored() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.onSubscribe(new BooleanSubscription()); + + ts.onError(new TestException()); + + try { + ts.assertValueSequenceOnly(Collections.<Integer>emptyList()); + throw new RuntimeException(); + } catch (AssertionError ex) { + // expected + } + } + + @Test(timeout = 1000) + public void awaitCount0() { + TestSubscriber<Integer> ts = TestSubscriber.create(); + ts.awaitCount(0, TestWaitStrategy.SLEEP_1MS, 0); + } + + @Test + public void assertValueSetWiderSet() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); + + Flowable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set); + } + + @Test + public void assertValueSetExact() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(1, 2, 3, 4, 5)); + + Flowable.just(4, 5, 1, 3, 2) + .test() + .assertValueSet(set) + .assertValueCount(set.size()); + } + + @Test + public void assertValueSetMissing() { + Set<Integer> set = new HashSet<Integer>(Arrays.asList(0, 1, 2, 4, 5, 6, 7)); + + try { + Flowable.range(1, 5) + .test() + .assertValueSet(set); + + throw new RuntimeException("Should have failed"); + } catch (AssertionError ex) { + assertTrue(ex.getMessage(), ex.getMessage().contains("Value not in the expected collection: " + 3)); + } + } } diff --git a/src/test/java/io/reactivex/tck/BaseTck.java b/src/test/java/io/reactivex/tck/BaseTck.java index 42df479448..a06522a399 100644 --- a/src/test/java/io/reactivex/tck/BaseTck.java +++ b/src/test/java/io/reactivex/tck/BaseTck.java @@ -44,7 +44,6 @@ public Publisher<T> createFailedPublisher() { return Flowable.error(new TestException()); } - @Override public long maxElementsFromPublisher() { return 1024; diff --git a/src/test/java/io/reactivex/tck/BehaviorProcessorAsPublisherTckTest.java b/src/test/java/io/reactivex/tck/BehaviorProcessorAsPublisherTckTest.java index cf1a47887e..4843f25ea7 100644 --- a/src/test/java/io/reactivex/tck/BehaviorProcessorAsPublisherTckTest.java +++ b/src/test/java/io/reactivex/tck/BehaviorProcessorAsPublisherTckTest.java @@ -22,6 +22,10 @@ @Test public class BehaviorProcessorAsPublisherTckTest extends BaseTck<Integer> { + public BehaviorProcessorAsPublisherTckTest() { + super(50); + } + @Override public Publisher<Integer> createPublisher(final long elements) { final BehaviorProcessor<Integer> pp = BehaviorProcessor.create(); diff --git a/src/test/java/io/reactivex/tck/CompletableAndThenPublisherTckTest.java b/src/test/java/io/reactivex/tck/CompletableAndThenPublisherTckTest.java new file mode 100644 index 0000000000..5a4c5f450c --- /dev/null +++ b/src/test/java/io/reactivex/tck/CompletableAndThenPublisherTckTest.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class CompletableAndThenPublisherTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(final long elements) { + return + Completable.complete().hide().andThen(Flowable.range(0, (int)elements)) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatMapMaybeTckTest.java b/src/test/java/io/reactivex/tck/ConcatMapMaybeTckTest.java new file mode 100644 index 0000000000..0e41d3f515 --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatMapMaybeTckTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@Test +public class ConcatMapMaybeTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(0, (int)elements) + .concatMapMaybe(new Function<Integer, Maybe<Integer>>() { + @Override + public Maybe<Integer> apply(Integer v) throws Exception { + return Maybe.just(v); + } + }) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatMapSingleTckTest.java b/src/test/java/io/reactivex/tck/ConcatMapSingleTckTest.java new file mode 100644 index 0000000000..dcb2564fba --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatMapSingleTckTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@Test +public class ConcatMapSingleTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(0, (int)elements) + .concatMapSingle(new Function<Integer, Single<Integer>>() { + @Override + public Single<Integer> apply(Integer v) throws Exception { + return Single.just(v); + } + }) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatWithCompletableTckTest.java b/src/test/java/io/reactivex/tck/ConcatWithCompletableTckTest.java new file mode 100644 index 0000000000..5a6b12cfeb --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatWithCompletableTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class ConcatWithCompletableTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(1, (int)elements) + .concatWith(Completable.complete()) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatWithMaybeEmptyTckTest.java b/src/test/java/io/reactivex/tck/ConcatWithMaybeEmptyTckTest.java new file mode 100644 index 0000000000..57d3440724 --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatWithMaybeEmptyTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class ConcatWithMaybeEmptyTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(1, (int)elements) + .concatWith(Maybe.<Integer>empty()) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatWithMaybeTckTest.java b/src/test/java/io/reactivex/tck/ConcatWithMaybeTckTest.java new file mode 100644 index 0000000000..e053a47811 --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatWithMaybeTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class ConcatWithMaybeTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(1, Math.max(0, (int)elements - 1)) + .concatWith(Maybe.just((int)elements)) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/ConcatWithSingleTckTest.java b/src/test/java/io/reactivex/tck/ConcatWithSingleTckTest.java new file mode 100644 index 0000000000..b46d344f15 --- /dev/null +++ b/src/test/java/io/reactivex/tck/ConcatWithSingleTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class ConcatWithSingleTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(1, Math.max(0, (int)elements - 1)) + .concatWith(Single.just((int)elements)) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/IntervalTckTest.java b/src/test/java/io/reactivex/tck/IntervalTckTest.java index cee8bf0415..6412a18a75 100644 --- a/src/test/java/io/reactivex/tck/IntervalTckTest.java +++ b/src/test/java/io/reactivex/tck/IntervalTckTest.java @@ -23,6 +23,10 @@ @Test public class IntervalTckTest extends BaseTck<Long> { + public IntervalTckTest() { + super(50); + } + @Override public Publisher<Long> createPublisher(long elements) { return diff --git a/src/test/java/io/reactivex/tck/LimitTckTest.java b/src/test/java/io/reactivex/tck/LimitTckTest.java new file mode 100644 index 0000000000..f12682396b --- /dev/null +++ b/src/test/java/io/reactivex/tck/LimitTckTest.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.Flowable; + +@Test +public class LimitTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(long elements) { + return + Flowable.range(0, (int)elements * 2).limit(elements) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MaybeFlatMapPublisherTckTest.java b/src/test/java/io/reactivex/tck/MaybeFlatMapPublisherTckTest.java new file mode 100644 index 0000000000..a05af6e197 --- /dev/null +++ b/src/test/java/io/reactivex/tck/MaybeFlatMapPublisherTckTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@Test +public class MaybeFlatMapPublisherTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(final long elements) { + return + Maybe.just(1).hide().flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.range(0, (int)elements); + } + }) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MergeWithCompletableTckTest.java b/src/test/java/io/reactivex/tck/MergeWithCompletableTckTest.java new file mode 100644 index 0000000000..30e67e95ac --- /dev/null +++ b/src/test/java/io/reactivex/tck/MergeWithCompletableTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class MergeWithCompletableTckTest extends BaseTck<Long> { + + @Override + public Publisher<Long> createPublisher(long elements) { + return + Flowable.rangeLong(1, elements) + .mergeWith(Completable.complete()) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MergeWithMaybeEmptyTckTest.java b/src/test/java/io/reactivex/tck/MergeWithMaybeEmptyTckTest.java new file mode 100644 index 0000000000..3d105c8842 --- /dev/null +++ b/src/test/java/io/reactivex/tck/MergeWithMaybeEmptyTckTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class MergeWithMaybeEmptyTckTest extends BaseTck<Long> { + + @Override + public Publisher<Long> createPublisher(long elements) { + return + Flowable.rangeLong(1, elements) + .mergeWith(Maybe.<Long>empty()) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MergeWithMaybeTckTest.java b/src/test/java/io/reactivex/tck/MergeWithMaybeTckTest.java new file mode 100644 index 0000000000..f5f7d4abef --- /dev/null +++ b/src/test/java/io/reactivex/tck/MergeWithMaybeTckTest.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class MergeWithMaybeTckTest extends BaseTck<Long> { + + @Override + public Publisher<Long> createPublisher(long elements) { + if (elements == 0) { + return Flowable.<Long>empty() + .mergeWith(Maybe.<Long>empty()); + } + return + Flowable.rangeLong(1, elements - 1) + .mergeWith(Maybe.just(elements)) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MergeWithSingleTckTest.java b/src/test/java/io/reactivex/tck/MergeWithSingleTckTest.java new file mode 100644 index 0000000000..28c3e8194b --- /dev/null +++ b/src/test/java/io/reactivex/tck/MergeWithSingleTckTest.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; + +@Test +public class MergeWithSingleTckTest extends BaseTck<Long> { + + @Override + public Publisher<Long> createPublisher(long elements) { + if (elements == 0) { + return Flowable.empty(); + } + return + Flowable.rangeLong(1, elements - 1) + .mergeWith(Single.just(elements)) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/MulticastProcessorAsPublisherTckTest.java b/src/test/java/io/reactivex/tck/MulticastProcessorAsPublisherTckTest.java new file mode 100644 index 0000000000..01f4710c58 --- /dev/null +++ b/src/test/java/io/reactivex/tck/MulticastProcessorAsPublisherTckTest.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.processors.MulticastProcessor; +import io.reactivex.schedulers.Schedulers; + +@Test +public class MulticastProcessorAsPublisherTckTest extends BaseTck<Integer> { + + public MulticastProcessorAsPublisherTckTest() { + super(100); + } + + @Override + public Publisher<Integer> createPublisher(final long elements) { + final MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + long start = System.currentTimeMillis(); + while (!mp.hasSubscribers()) { + try { + Thread.sleep(1); + } catch (InterruptedException ex) { + return; + } + + if (System.currentTimeMillis() - start > 200) { + return; + } + } + + for (int i = 0; i < elements; i++) { + while (!mp.offer(i)) { + Thread.yield(); + if (System.currentTimeMillis() - start > 1000) { + return; + } + } + } + mp.onComplete(); + } + }); + return mp; + } +} diff --git a/src/test/java/io/reactivex/tck/MulticastProcessorRefCountedTckTest.java b/src/test/java/io/reactivex/tck/MulticastProcessorRefCountedTckTest.java new file mode 100644 index 0000000000..0e61371f7e --- /dev/null +++ b/src/test/java/io/reactivex/tck/MulticastProcessorRefCountedTckTest.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import java.util.concurrent.*; + +import org.reactivestreams.*; +import org.reactivestreams.tck.*; +import org.testng.annotations.Test; + +import io.reactivex.exceptions.TestException; +import io.reactivex.processors.*; + +@Test +public class MulticastProcessorRefCountedTckTest extends IdentityProcessorVerification<Integer> { + + public MulticastProcessorRefCountedTckTest() { + super(new TestEnvironment(200)); + } + + @Override + public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) { + MulticastProcessor<Integer> mp = MulticastProcessor.create(true); + return mp; + } + + @Override + public Publisher<Integer> createFailedPublisher() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + mp.onError(new TestException()); + return mp; + } + + @Override + public ExecutorService publisherExecutorService() { + return Executors.newCachedThreadPool(); + } + + @Override + public Integer createElement(int element) { + return element; + } + + @Override + public long maxSupportedSubscribers() { + return 1; + } + + @Override + public long maxElementsFromPublisher() { + return 1024; + } +} diff --git a/src/test/java/io/reactivex/tck/MulticastProcessorTckTest.java b/src/test/java/io/reactivex/tck/MulticastProcessorTckTest.java new file mode 100644 index 0000000000..53dd476e6f --- /dev/null +++ b/src/test/java/io/reactivex/tck/MulticastProcessorTckTest.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import java.util.concurrent.*; + +import org.reactivestreams.*; +import org.reactivestreams.tck.*; +import org.testng.annotations.Test; + +import io.reactivex.exceptions.TestException; +import io.reactivex.processors.*; + +@Test +public class MulticastProcessorTckTest extends IdentityProcessorVerification<Integer> { + + public MulticastProcessorTckTest() { + super(new TestEnvironment(50)); + } + + @Override + public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + return new RefCountProcessor<Integer>(mp); + } + + @Override + public Publisher<Integer> createFailedPublisher() { + MulticastProcessor<Integer> mp = MulticastProcessor.create(); + mp.start(); + mp.onError(new TestException()); + return mp; + } + + @Override + public ExecutorService publisherExecutorService() { + return Executors.newCachedThreadPool(); + } + + @Override + public Integer createElement(int element) { + return element; + } + + @Override + public long maxSupportedSubscribers() { + return 1; + } + + @Override + public long maxElementsFromPublisher() { + return 1024; + } +} diff --git a/src/test/java/io/reactivex/tck/RefCountProcessor.java b/src/test/java/io/reactivex/tck/RefCountProcessor.java new file mode 100644 index 0000000000..de0538064b --- /dev/null +++ b/src/test/java/io/reactivex/tck/RefCountProcessor.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.processors.FlowableProcessor; + +/** + * A FlowableProcessor wrapper that disposes the Subscription set via + * onSubscribe if the number of subscribers reaches zero. + * + * @param <T> the upstream and downstream value type + * @since 2.1.8 + */ +/* public */final class RefCountProcessor<T> extends FlowableProcessor<T> implements Subscription { + + final FlowableProcessor<T> actual; + + final AtomicReference<Subscription> upstream; + + final AtomicReference<RefCountSubscriber<T>[]> subscribers; + + @SuppressWarnings("rawtypes") + static final RefCountSubscriber[] EMPTY = new RefCountSubscriber[0]; + + @SuppressWarnings("rawtypes") + static final RefCountSubscriber[] TERMINATED = new RefCountSubscriber[0]; + + @SuppressWarnings("unchecked") + RefCountProcessor(FlowableProcessor<T> actual) { + this.actual = actual; + this.upstream = new AtomicReference<Subscription>(); + this.subscribers = new AtomicReference<RefCountSubscriber<T>[]>(EMPTY); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(upstream, s)) { + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + upstream.lazySet(SubscriptionHelper.CANCELLED); + actual.onError(t); + } + + @Override + public void onComplete() { + upstream.lazySet(SubscriptionHelper.CANCELLED); + actual.onComplete(); + } + + @Override + protected void subscribeActual(Subscriber<? super T> s) { + RefCountSubscriber<T> rcs = new RefCountSubscriber<T>(s, this); + if (!add(rcs)) { + EmptySubscription.error(new IllegalStateException("RefCountProcessor terminated"), s); + return; + } + actual.subscribe(rcs); + } + + @Override + public boolean hasComplete() { + return actual.hasComplete(); + } + + @Override + public boolean hasThrowable() { + return actual.hasThrowable(); + } + + @Override + public Throwable getThrowable() { + return actual.getThrowable(); + } + + @Override + public boolean hasSubscribers() { + return actual.hasSubscribers(); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + } + + @Override + public void request(long n) { + upstream.get().request(n); + } + + boolean add(RefCountSubscriber<T> rcs) { + for (;;) { + RefCountSubscriber<T>[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + RefCountSubscriber<T>[] b = new RefCountSubscriber[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = rcs; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(RefCountSubscriber<T> rcs) { + for (;;) { + RefCountSubscriber<T>[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + break; + } + int j = -1; + + for (int i = 0; i < n; i++) { + if (rcs == a[i]) { + j = i; + break; + } + } + + if (j < 0) { + break; + } + + RefCountSubscriber<T>[] b; + if (n == 1) { + b = TERMINATED; + } else { + b = new RefCountSubscriber[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + if (b == TERMINATED) { + cancel(); + } + break; + } + } + } + + static final class RefCountSubscriber<T> extends AtomicBoolean implements FlowableSubscriber<T>, Subscription { + + private static final long serialVersionUID = -4317488092687530631L; + + final Subscriber<? super T> downstream; + + final RefCountProcessor<T> parent; + + Subscription upstream; + + RefCountSubscriber(Subscriber<? super T> actual, RefCountProcessor<T> parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + lazySet(true); + upstream.cancel(); + parent.remove(this); + } + + @Override + public void onSubscribe(Subscription s) { + this.upstream = s; + downstream.onSubscribe(this); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/test/java/io/reactivex/tck/SingleFlatMapFlowableTckTest.java b/src/test/java/io/reactivex/tck/SingleFlatMapFlowableTckTest.java new file mode 100644 index 0000000000..009bd5e50f --- /dev/null +++ b/src/test/java/io/reactivex/tck/SingleFlatMapFlowableTckTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import org.reactivestreams.Publisher; +import org.testng.annotations.Test; + +import io.reactivex.*; +import io.reactivex.functions.Function; + +@Test +public class SingleFlatMapFlowableTckTest extends BaseTck<Integer> { + + @Override + public Publisher<Integer> createPublisher(final long elements) { + return + Single.just(1).hide().flatMapPublisher(new Function<Integer, Publisher<Integer>>() { + @Override + public Publisher<Integer> apply(Integer v) + throws Exception { + return Flowable.range(0, (int)elements); + } + }) + ; + } +} diff --git a/src/test/java/io/reactivex/tck/UnicastProcessorTckTest.java b/src/test/java/io/reactivex/tck/UnicastProcessorTckTest.java new file mode 100644 index 0000000000..e6490905ea --- /dev/null +++ b/src/test/java/io/reactivex/tck/UnicastProcessorTckTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.tck; + +import java.util.concurrent.*; + +import org.reactivestreams.*; +import org.reactivestreams.tck.*; +import org.testng.annotations.Test; + +import io.reactivex.exceptions.TestException; +import io.reactivex.processors.UnicastProcessor; + +@Test +public class UnicastProcessorTckTest extends IdentityProcessorVerification<Integer> { + + public UnicastProcessorTckTest() { + super(new TestEnvironment(50)); + } + + @Override + public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) { + UnicastProcessor<Integer> up = UnicastProcessor.create(); + return new RefCountProcessor<Integer>(up); + } + + @Override + public Publisher<Integer> createFailedPublisher() { + UnicastProcessor<Integer> up = UnicastProcessor.create(); + up.onError(new TestException()); + return up; + } + + @Override + public ExecutorService publisherExecutorService() { + return Executors.newCachedThreadPool(); + } + + @Override + public Integer createElement(int element) { + return element; + } + + @Override + public long maxSupportedSubscribers() { + return 1; + } + + @Override + public long maxElementsFromPublisher() { + return 1024; + } +} diff --git a/src/test/java/io/reactivex/BaseTypeAnnotations.java b/src/test/java/io/reactivex/validators/BaseTypeAnnotations.java similarity index 99% rename from src/test/java/io/reactivex/BaseTypeAnnotations.java rename to src/test/java/io/reactivex/validators/BaseTypeAnnotations.java index 609da2b15a..5445115b44 100644 --- a/src/test/java/io/reactivex/BaseTypeAnnotations.java +++ b/src/test/java/io/reactivex/validators/BaseTypeAnnotations.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import static org.junit.Assert.fail; @@ -20,6 +20,7 @@ import org.junit.Test; import org.reactivestreams.Publisher; +import io.reactivex.*; import io.reactivex.annotations.*; /** diff --git a/src/test/java/io/reactivex/BaseTypeParser.java b/src/test/java/io/reactivex/validators/BaseTypeParser.java similarity index 99% rename from src/test/java/io/reactivex/BaseTypeParser.java rename to src/test/java/io/reactivex/validators/BaseTypeParser.java index 55067f74ce..42903ee386 100644 --- a/src/test/java/io/reactivex/BaseTypeParser.java +++ b/src/test/java/io/reactivex/validators/BaseTypeParser.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.File; import java.util.*; diff --git a/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java b/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java new file mode 100644 index 0000000000..8f8acaf3d4 --- /dev/null +++ b/src/test/java/io/reactivex/validators/CheckLocalVariablesInTests.java @@ -0,0 +1,426 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.validators; + +import java.io.*; +import java.util.*; +import java.util.regex.Pattern; + +import org.junit.Test; + +/** + * Checks for commonly copy-pasted but not-renamed local variables in unit tests. + * <ul> + * <li>{@code TestSubscriber} named as {@code to*}</li> + * <li>{@code TestObserver} named as {@code ts*}</li> + * <li>{@code PublishProcessor} named as {@code ps*}</li> + * <li>{@code PublishSubject} named as {@code pp*}</li> + * <li>{@code Subscription} with single letter name such as "s" or "d"</li> + * <li>{@code Disposable} with single letter name such as "s" or "d"</li> + * <li>{@code Flowable} named as {@code o|observable} + number</li> + * <li>{@code Observable} named as {@code f|flowable} + number</li> + * <li>{@code Subscriber} named as "o" or "observer"</li> + * <li>{@code Observer} named as "s" or "subscriber"</li> + * </ul> + */ +public class CheckLocalVariablesInTests { + + static void findPattern(String pattern) throws Exception { + findPattern(pattern, false); + } + + static void findPattern(String pattern, boolean checkMain) throws Exception { + File f = MaybeNo2Dot0Since.findSource("Flowable"); + if (f == null) { + System.out.println("Unable to find sources of RxJava"); + return; + } + + Queue<File> dirs = new ArrayDeque<File>(); + + StringBuilder fail = new StringBuilder(); + fail.append("The following code pattern was found: ").append(pattern).append("\n"); + + File parent = f.getParentFile(); + + if (checkMain) { + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/'))); + } + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java"))); + + Pattern p = Pattern.compile(pattern); + + int total = 0; + + while (!dirs.isEmpty()) { + f = dirs.poll(); + + File[] list = f.listFiles(); + if (list != null && list.length != 0) { + + for (File u : list) { + if (u.isDirectory()) { + dirs.offer(u); + } else { + String fname = u.getName(); + if (fname.endsWith(".java")) { + + int lineNum = 0; + BufferedReader in = new BufferedReader(new FileReader(u)); + try { + for (;;) { + String line = in.readLine(); + if (line != null) { + lineNum++; + + line = line.trim(); + + if (!line.startsWith("//") && !line.startsWith("*")) { + if (p.matcher(line).find()) { + fail + .append(fname) + .append("#L").append(lineNum) + .append(" ").append(line) + .append("\n"); + total++; + } + } + } else { + break; + } + } + } finally { + in.close(); + } + } + } + } + } + } + if (total != 0) { + fail.append("Found ") + .append(total) + .append(" instances"); + System.out.println(fail); + throw new AssertionError(fail.toString()); + } + } + + @Test + public void testSubscriberAsTo() throws Exception { + findPattern("TestSubscriber<.*>\\s+to"); + } + + @Test + public void testObserverAsTs() throws Exception { + findPattern("TestObserver<.*>\\s+ts"); + } + + @Test + public void testSubscriberNoArgAsTo() throws Exception { + findPattern("TestSubscriber\\s+to"); + } + + @Test + public void testObserverNoArgAsTs() throws Exception { + findPattern("TestObserver\\s+ts"); + } + + @Test + public void publishSubjectAsPp() throws Exception { + findPattern("PublishSubject<.*>\\s+pp"); + } + + @Test + public void publishProcessorAsPs() throws Exception { + findPattern("PublishProcessor<.*>\\s+ps"); + } + + @Test + public void behaviorProcessorAsBs() throws Exception { + findPattern("BehaviorProcessor<.*>\\s+bs"); + } + + @Test + public void behaviorSubjectAsBp() throws Exception { + findPattern("BehaviorSubject<.*>\\s+bp"); + } + + @Test + public void connectableFlowableAsCo() throws Exception { + findPattern("ConnectableFlowable<.*>\\s+co(0-9|\\b)"); + } + + @Test + public void connectableObservableAsCf() throws Exception { + findPattern("ConnectableObservable<.*>\\s+cf(0-9|\\b)"); + } + + @Test + public void queueDisposableInsteadOfQueueFuseable() throws Exception { + findPattern("QueueDisposable\\.(NONE|SYNC|ASYNC|ANY|BOUNDARY)"); + } + + @Test + public void queueSubscriptionInsteadOfQueueFuseable() throws Exception { + findPattern("QueueSubscription\\.(NONE|SYNC|ASYNC|ANY|BOUNDARY)"); + } + + @Test + public void singleSourceAsMs() throws Exception { + findPattern("SingleSource<.*>\\s+ms"); + } + + @Test + public void singleSourceAsCs() throws Exception { + findPattern("SingleSource<.*>\\s+cs"); + } + + @Test + public void maybeSourceAsSs() throws Exception { + findPattern("MaybeSource<.*>\\s+ss"); + } + + @Test + public void maybeSourceAsCs() throws Exception { + findPattern("MaybeSource<.*>\\s+cs"); + } + + @Test + public void completableSourceAsSs() throws Exception { + findPattern("CompletableSource<.*>\\s+ss"); + } + + @Test + public void completableSourceAsMs() throws Exception { + findPattern("CompletableSource<.*>\\s+ms"); + } + + @Test + public void observableAsC() throws Exception { + findPattern("Observable<.*>\\s+c\\b"); + } + + @Test + public void subscriberAsObserver() throws Exception { + findPattern("Subscriber<.*>\\s+observer[0-9]?\\b"); + } + + @Test + public void subscriberAsO() throws Exception { + findPattern("Subscriber<.*>\\s+o[0-9]?\\b"); + } + + @Test + public void singleAsObservable() throws Exception { + findPattern("Single<.*>\\s+observable\\b"); + } + + @Test + public void singleAsFlowable() throws Exception { + findPattern("Single<.*>\\s+flowable\\b"); + } + + @Test + public void observerAsSubscriber() throws Exception { + findPattern("Observer<.*>\\s+subscriber[0-9]?\\b"); + } + + @Test + public void observerAsS() throws Exception { + findPattern("Observer<.*>\\s+s[0-9]?\\b"); + } + + @Test + public void observerNoArgAsSubscriber() throws Exception { + findPattern("Observer\\s+subscriber[0-9]?\\b"); + } + + @Test + public void observerNoArgAsS() throws Exception { + findPattern("Observer\\s+s[0-9]?\\b"); + } + + @Test + public void flowableAsObservable() throws Exception { + findPattern("Flowable<.*>\\s+observable[0-9]?\\b"); + } + + @Test + public void flowableAsO() throws Exception { + findPattern("Flowable<.*>\\s+o[0-9]?\\b"); + } + + @Test + public void flowableNoArgAsO() throws Exception { + findPattern("Flowable\\s+o[0-9]?\\b"); + } + + @Test + public void flowableNoArgAsObservable() throws Exception { + findPattern("Flowable\\s+observable[0-9]?\\b"); + } + + @Test + public void processorAsSubject() throws Exception { + findPattern("Processor<.*>\\s+subject(0-9)?\\b"); + } + + @Test + public void maybeAsObservable() throws Exception { + findPattern("Maybe<.*>\\s+observable\\b"); + } + + @Test + public void maybeAsFlowable() throws Exception { + findPattern("Maybe<.*>\\s+flowable\\b"); + } + + @Test + public void completableAsObservable() throws Exception { + findPattern("Completable\\s+observable\\b"); + } + + @Test + public void completableAsFlowable() throws Exception { + findPattern("Completable\\s+flowable\\b"); + } + + @Test + public void subscriptionAsFieldS() throws Exception { + findPattern("Subscription\\s+s[0-9]?;", true); + } + + @Test + public void subscriptionAsD() throws Exception { + findPattern("Subscription\\s+d[0-9]?", true); + } + + @Test + public void subscriptionAsSubscription() throws Exception { + findPattern("Subscription\\s+subscription[0-9]?;", true); + } + + @Test + public void subscriptionAsDParenthesis() throws Exception { + findPattern("Subscription\\s+d[0-9]?\\)", true); + } + + @Test + public void queueSubscriptionAsD() throws Exception { + findPattern("Subscription<.*>\\s+q?d[0-9]?\\b", true); + } + + @Test + public void booleanSubscriptionAsbd() throws Exception { + findPattern("BooleanSubscription\\s+bd[0-9]?;", true); + } + + @Test + public void atomicSubscriptionAsS() throws Exception { + findPattern("AtomicReference<Subscription>\\s+s[0-9]?;", true); + } + + @Test + public void atomicSubscriptionAsSInit() throws Exception { + findPattern("AtomicReference<Subscription>\\s+s[0-9]?\\s", true); + } + + @Test + public void atomicSubscriptionAsSubscription() throws Exception { + findPattern("AtomicReference<Subscription>\\s+subscription[0-9]?", true); + } + + @Test + public void atomicSubscriptionAsD() throws Exception { + findPattern("AtomicReference<Subscription>\\s+d[0-9]?", true); + } + + @Test + public void disposableAsS() throws Exception { + // the space before makes sure it doesn't match onSubscribe(Subscription) unnecessarily + findPattern("Disposable\\s+s[0-9]?\\b", true); + } + + @Test + public void disposableAsFieldD() throws Exception { + findPattern("Disposable\\s+d[0-9]?;", true); + } + + @Test + public void atomicDisposableAsS() throws Exception { + findPattern("AtomicReference<Disposable>\\s+s[0-9]?", true); + } + + @Test + public void atomicDisposableAsD() throws Exception { + findPattern("AtomicReference<Disposable>\\s+d[0-9]?;", true); + } + + @Test + public void subscriberAsFieldActual() throws Exception { + findPattern("Subscriber<.*>\\s+actual[;\\)]", true); + } + + @Test + public void subscriberNoArgAsFieldActual() throws Exception { + findPattern("Subscriber\\s+actual[;\\)]", true); + } + + @Test + public void subscriberAsFieldS() throws Exception { + findPattern("Subscriber<.*>\\s+s[0-9]?;", true); + } + + @Test + public void observerAsFieldActual() throws Exception { + findPattern("Observer<.*>\\s+actual[;\\)]", true); + } + + @Test + public void observerAsFieldSO() throws Exception { + findPattern("Observer<.*>\\s+[so][0-9]?;", true); + } + + @Test + public void observerNoArgAsFieldActual() throws Exception { + findPattern("Observer\\s+actual[;\\)]", true); + } + + @Test + public void observerNoArgAsFieldCs() throws Exception { + findPattern("Observer\\s+cs[;\\)]", true); + } + + @Test + public void observerNoArgAsFieldSO() throws Exception { + findPattern("Observer\\s+[so][0-9]?;", true); + } + + @Test + public void queueDisposableAsD() throws Exception { + findPattern("Disposable<.*>\\s+q?s[0-9]?\\b", true); + } + + @Test + public void disposableAsDParenthesis() throws Exception { + findPattern("Disposable\\s+s[0-9]?\\)", true); + } + + @Test + public void compositeDisposableAsCs() throws Exception { + findPattern("CompositeDisposable\\s+cs[0-9]?", true); + } + +} diff --git a/src/test/java/io/reactivex/FixLicenseHeaders.java b/src/test/java/io/reactivex/validators/FixLicenseHeaders.java similarity index 99% rename from src/test/java/io/reactivex/FixLicenseHeaders.java rename to src/test/java/io/reactivex/validators/FixLicenseHeaders.java index 6cd016a593..3c96e5615e 100644 --- a/src/test/java/io/reactivex/FixLicenseHeaders.java +++ b/src/test/java/io/reactivex/validators/FixLicenseHeaders.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.*; import java.util.*; diff --git a/src/test/java/io/reactivex/InternalWrongNaming.java b/src/test/java/io/reactivex/validators/InternalWrongNaming.java similarity index 94% rename from src/test/java/io/reactivex/InternalWrongNaming.java rename to src/test/java/io/reactivex/validators/InternalWrongNaming.java index 846a1d08e0..cbe1cbe104 100644 --- a/src/test/java/io/reactivex/InternalWrongNaming.java +++ b/src/test/java/io/reactivex/validators/InternalWrongNaming.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.*; import java.util.*; @@ -179,7 +179,13 @@ public void flowableNoObserver() throws Exception { "FlowableFlatMapCompletableCompletable", "FlowableFlatMapSingle", "FlowableFlatMapMaybe", - "FlowableSequenceEqualSingle" + "FlowableSequenceEqualSingle", + "FlowableConcatWithSingle", + "FlowableConcatWithMaybe", + "FlowableConcatWithCompletable", + "FlowableMergeWithSingle", + "FlowableMergeWithMaybe", + "FlowableMergeWithCompletable" ); } } diff --git a/src/test/java/io/reactivex/JavadocFindUnescapedAngleBrackets.java b/src/test/java/io/reactivex/validators/JavadocFindUnescapedAngleBrackets.java similarity index 99% rename from src/test/java/io/reactivex/JavadocFindUnescapedAngleBrackets.java rename to src/test/java/io/reactivex/validators/JavadocFindUnescapedAngleBrackets.java index bf4d2cbff5..1d8354186d 100644 --- a/src/test/java/io/reactivex/JavadocFindUnescapedAngleBrackets.java +++ b/src/test/java/io/reactivex/validators/JavadocFindUnescapedAngleBrackets.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.*; import java.util.*; diff --git a/src/test/java/io/reactivex/JavadocForAnnotations.java b/src/test/java/io/reactivex/validators/JavadocForAnnotations.java similarity index 97% rename from src/test/java/io/reactivex/JavadocForAnnotations.java rename to src/test/java/io/reactivex/validators/JavadocForAnnotations.java index 45aae9b97e..3912ced5ce 100644 --- a/src/test/java/io/reactivex/JavadocForAnnotations.java +++ b/src/test/java/io/reactivex/validators/JavadocForAnnotations.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import static org.junit.Assert.fail; @@ -19,6 +19,8 @@ import org.junit.*; +import io.reactivex.*; + /** * Checks the source code of the base reactive types and locates missing * mention of {@code Backpressure:} and {@code Scheduler:} of methods. @@ -102,7 +104,6 @@ static final void scanFor(StringBuilder sourceCode, String annotation, String in } } - static final void scanForBadMethod(StringBuilder sourceCode, String annotation, String inDoc, StringBuilder e, String baseClassName) { int index = 0; @@ -127,8 +128,10 @@ static final void scanForBadMethod(StringBuilder sourceCode, String annotation, if ((ll < 0 || ll > idx) && (lm < 0 || lm > idx)) { int n = sourceCode.indexOf("{@code ", k); + int endDD = sourceCode.indexOf("</dd>", k); + // make sure the {@code is within the dt/dd section - if (n < idx) { + if (n < idx && n < endDD) { int m = sourceCode.indexOf("}", n); if (m < idx) { diff --git a/src/test/java/io/reactivex/JavadocWording.java b/src/test/java/io/reactivex/validators/JavadocWording.java similarity index 90% rename from src/test/java/io/reactivex/JavadocWording.java rename to src/test/java/io/reactivex/validators/JavadocWording.java index 627b2b1732..c8d01d7b61 100644 --- a/src/test/java/io/reactivex/JavadocWording.java +++ b/src/test/java/io/reactivex/validators/JavadocWording.java @@ -11,14 +11,15 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.util.List; +import java.util.regex.Pattern; import static org.junit.Assert.*; import org.junit.Test; -import io.reactivex.BaseTypeParser.RxMethod; +import io.reactivex.validators.BaseTypeParser.RxMethod; /** * Check if the method wording is consistent with the target base type. @@ -148,9 +149,11 @@ public void maybeDocRefersToMaybeTypes() throws Exception { for (;;) { int idx = m.javadoc.indexOf("Single", jdx); if (idx >= 0) { - if (!m.signature.contains("Single")) { + int j = m.javadoc.indexOf("#toSingle", jdx); + int k = m.javadoc.indexOf("{@code Single", jdx); + if (!m.signature.contains("Single") && (j + 3 != idx && k + 7 != idx)) { e.append("java.lang.RuntimeException: Maybe doc mentions Single but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + .append("Maybe(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -693,8 +696,11 @@ public void completableDocRefersToCompletableTypes() throws Exception { int idx = m.javadoc.indexOf("Flowable", jdx); if (idx >= 0) { if (!m.signature.contains("Flowable")) { - e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Flowable"); + if (!p.matcher(m.javadoc).find()) { + e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.") + .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + } } jdx = idx + 6; } else { @@ -706,8 +712,11 @@ public void completableDocRefersToCompletableTypes() throws Exception { int idx = m.javadoc.indexOf("Single", jdx); if (idx >= 0) { if (!m.signature.contains("Single")) { - e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Single"); + if (!p.matcher(m.javadoc).find()) { + e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.") + .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + } } jdx = idx + 6; } else { @@ -719,8 +728,11 @@ public void completableDocRefersToCompletableTypes() throws Exception { int idx = m.javadoc.indexOf("SingleSource", jdx); if (idx >= 0) { if (!m.signature.contains("SingleSource")) { - e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*SingleSource"); + if (!p.matcher(m.javadoc).find()) { + e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.") + .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + } } jdx = idx + 6; } else { @@ -732,8 +744,11 @@ public void completableDocRefersToCompletableTypes() throws Exception { int idx = m.javadoc.indexOf(" Observable", jdx); if (idx >= 0) { if (!m.signature.contains("Observable")) { - e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Observable"); + if (!p.matcher(m.javadoc).find()) { + e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.") + .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + } } jdx = idx + 6; } else { @@ -745,8 +760,11 @@ public void completableDocRefersToCompletableTypes() throws Exception { int idx = m.javadoc.indexOf("ObservableSource", jdx); if (idx >= 0) { if (!m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*ObservableSource"); + if (!p.matcher(m.javadoc).find()) { + e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") + .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + } } jdx = idx + 6; } else { @@ -798,6 +816,7 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str } } + jdx = 0; for (;;) { idx = m.javadoc.indexOf(wrongPre + " {@link " + word, jdx); if (idx >= 0) { @@ -814,6 +833,7 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str } } + jdx = 0; for (;;) { idx = m.javadoc.indexOf(wrongPre + " {@linkplain " + word, jdx); if (idx >= 0) { @@ -830,6 +850,7 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str } } + jdx = 0; for (;;) { idx = m.javadoc.indexOf(wrongPre + " {@code " + word, jdx); if (idx >= 0) { @@ -846,6 +867,42 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str } } + // remove linebreaks and multi-spaces + String javadoc2 = m.javadoc.replace("\n", " ").replace("\r", " ") + .replace(" * ", " ") + .replaceAll("\\s+", " "); + + // strip {@xxx } tags + int kk = 0; + for (;;) { + int jj = javadoc2.indexOf("{@", kk); + if (jj < 0) { + break; + } + int nn = javadoc2.indexOf(" ", jj + 2); + int mm = javadoc2.indexOf("}", jj + 2); + + javadoc2 = javadoc2.substring(0, jj) + javadoc2.substring(nn + 1, mm) + javadoc2.substring(mm + 1); + + kk = mm + 1; + } + + jdx = 0; + for (;;) { + idx = javadoc2.indexOf(wrongPre + " " + word, jdx); + if (idx >= 0) { + e.append("java.lang.RuntimeException: a/an typo ") + .append(word) + .append("\r\n at io.reactivex.") + .append(baseTypeName) + .append(" (") + .append(baseTypeName) + .append(".java:").append(m.javadocLine).append(")\r\n\r\n"); + jdx = idx + wrongPre.length() + 1 + word.length(); + } else { + break; + } + } } static void missingClosingDD(StringBuilder e, RxMethod m, String baseTypeName) { diff --git a/src/test/java/io/reactivex/MaybeNo2Dot0Since.java b/src/test/java/io/reactivex/validators/MaybeNo2Dot0Since.java similarity index 98% rename from src/test/java/io/reactivex/MaybeNo2Dot0Since.java rename to src/test/java/io/reactivex/validators/MaybeNo2Dot0Since.java index 774f4ee920..3ee13fbb18 100644 --- a/src/test/java/io/reactivex/MaybeNo2Dot0Since.java +++ b/src/test/java/io/reactivex/validators/MaybeNo2Dot0Since.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import static org.junit.Assert.fail; @@ -20,6 +20,8 @@ import org.junit.Test; +import io.reactivex.Maybe; + /** * Checks the source code of Maybe and finds unnecessary since 2.0 annotations in the * method's javadocs. diff --git a/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java b/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java new file mode 100644 index 0000000000..e7c1c13bb3 --- /dev/null +++ b/src/test/java/io/reactivex/validators/NewLinesBeforeAnnotation.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.validators; + +import java.io.*; +import java.util.*; + +import org.junit.Test; + +/** + * These tests verify the code style that a typical closing curly brace + * and the next annotation @ indicator + * are not separated by less than or more than one empty line. + * <p>Thus this is detected: + * <pre><code> + * } + * @Override + * </code></pre> + * <p> + * as well as + * <pre><code> + * } + * + * + * @Override + * </code></pre> + */ +public class NewLinesBeforeAnnotation { + + @Test + public void missingEmptyNewLine() throws Exception { + findPattern(0); + } + + @Test + public void tooManyEmptyNewLines2() throws Exception { + findPattern(2); + } + + @Test + public void tooManyEmptyNewLines3() throws Exception { + findPattern(3); + } + + @Test + public void tooManyEmptyNewLines4() throws Exception { + findPattern(4); + } + + @Test + public void tooManyEmptyNewLines5() throws Exception { + findPattern(5); + } + + static void findPattern(int newLines) throws Exception { + File f = MaybeNo2Dot0Since.findSource("Flowable"); + if (f == null) { + System.out.println("Unable to find sources of RxJava"); + return; + } + + Queue<File> dirs = new ArrayDeque<File>(); + + StringBuilder fail = new StringBuilder(); + fail.append("The following code pattern was found: "); + fail.append("\\}\\R"); + for (int i = 0; i < newLines; i++) { + fail.append("\\R"); + } + fail.append("[ ]+@\n"); + + File parent = f.getParentFile(); + + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/'))); + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java"))); + + int total = 0; + + while (!dirs.isEmpty()) { + f = dirs.poll(); + + File[] list = f.listFiles(); + if (list != null && list.length != 0) { + + for (File u : list) { + if (u.isDirectory()) { + dirs.offer(u); + } else { + String fname = u.getName(); + if (fname.endsWith(".java")) { + + List<String> lines = new ArrayList<String>(); + BufferedReader in = new BufferedReader(new FileReader(u)); + try { + for (;;) { + String line = in.readLine(); + if (line == null) { + break; + } + lines.add(line); + } + } finally { + in.close(); + } + + for (int i = 0; i < lines.size() - 1; i++) { + String line = lines.get(i); + if (line.endsWith("}") && !line.trim().startsWith("*") && !line.trim().startsWith("//")) { + int emptyLines = 0; + boolean found = false; + for (int j = i + 1; j < lines.size(); j++) { + String line2 = lines.get(j); + if (line2.trim().startsWith("@")) { + found = true; + break; + } + if (!line2.trim().isEmpty()) { + break; + } + emptyLines++; + } + + if (emptyLines == newLines && found) { + fail + .append(fname) + .append("#L").append(i + 1) + .append(" "); + for (int k = 0; k < emptyLines + 2; k++) { + fail + .append(lines.get(k + i)) + .append("\\R"); + } + fail.append("\n"); + total++; + } + } + } + } + } + } + } + } + if (total != 0) { + fail.append("Found ") + .append(total) + .append(" instances"); + System.out.println(fail); + throw new AssertionError(fail.toString()); + } + } +} diff --git a/src/test/java/io/reactivex/NoAnonymousInnerClassesTest.java b/src/test/java/io/reactivex/validators/NoAnonymousInnerClassesTest.java similarity index 98% rename from src/test/java/io/reactivex/NoAnonymousInnerClassesTest.java rename to src/test/java/io/reactivex/validators/NoAnonymousInnerClassesTest.java index c611000c57..e26e3fb91d 100644 --- a/src/test/java/io/reactivex/NoAnonymousInnerClassesTest.java +++ b/src/test/java/io/reactivex/validators/NoAnonymousInnerClassesTest.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.File; import java.net.URL; diff --git a/src/test/java/io/reactivex/OperatorsAreFinal.java b/src/test/java/io/reactivex/validators/OperatorsAreFinal.java similarity index 98% rename from src/test/java/io/reactivex/OperatorsAreFinal.java rename to src/test/java/io/reactivex/validators/OperatorsAreFinal.java index ae1df8a499..5a149a94c5 100644 --- a/src/test/java/io/reactivex/OperatorsAreFinal.java +++ b/src/test/java/io/reactivex/validators/OperatorsAreFinal.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.File; import java.lang.reflect.Modifier; diff --git a/src/test/java/io/reactivex/ParamValidationCheckerTest.java b/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java similarity index 94% rename from src/test/java/io/reactivex/ParamValidationCheckerTest.java rename to src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java index 3013dda85b..2d7a9649b8 100644 --- a/src/test/java/io/reactivex/ParamValidationCheckerTest.java +++ b/src/test/java/io/reactivex/validators/ParamValidationCheckerTest.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.lang.reflect.*; import java.util.*; @@ -20,6 +20,9 @@ import org.junit.Test; import org.reactivestreams.*; +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; @@ -182,8 +185,9 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "take", Long.TYPE, TimeUnit.class)); addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "take", Long.TYPE, TimeUnit.class, Scheduler.class)); - // zero retry is allowed + // zero take/limit is allowed addOverride(new ParamOverride(Flowable.class, 0, ParamMode.NON_NEGATIVE, "take", Long.TYPE)); + addOverride(new ParamOverride(Flowable.class, 0, ParamMode.NON_NEGATIVE, "limit", Long.TYPE)); // negative time is considered as zero time addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "sample", Long.TYPE, TimeUnit.class)); @@ -225,6 +229,11 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLast", Long.TYPE, TimeUnit.class)); addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLast", Long.TYPE, TimeUnit.class, Scheduler.class)); + // negative time is considered as zero time + addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class)); + addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Scheduler.class)); + addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Boolean.TYPE)); + addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); // negative buffer time is considered as zero buffer time addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "window", Long.TYPE, TimeUnit.class)); @@ -252,11 +261,16 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class)); addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); + // negative time is considered as zero time + addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delaySubscription", Long.TYPE, TimeUnit.class)); + addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "delaySubscription", Long.TYPE, TimeUnit.class, Scheduler.class)); + // zero repeat is allowed addOverride(new ParamOverride(Completable.class, 0, ParamMode.NON_NEGATIVE, "repeat", Long.TYPE)); // zero retry is allowed addOverride(new ParamOverride(Completable.class, 0, ParamMode.NON_NEGATIVE, "retry", Long.TYPE)); + addOverride(new ParamOverride(Completable.class, 0, ParamMode.NON_NEGATIVE, "retry", Long.TYPE, Predicate.class)); // negative time is considered as zero time addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "blockingGet", Long.TYPE, TimeUnit.class)); @@ -312,14 +326,16 @@ public void checkParallelFlowable() { // negative time is considered as zero time addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class)); + addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Boolean.TYPE)); addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class)); - + addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delay", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); // zero repeat is allowed addOverride(new ParamOverride(Single.class, 0, ParamMode.NON_NEGATIVE, "repeat", Long.TYPE)); // zero retry is allowed addOverride(new ParamOverride(Single.class, 0, ParamMode.NON_NEGATIVE, "retry", Long.TYPE)); + addOverride(new ParamOverride(Single.class, 0, ParamMode.NON_NEGATIVE, "retry", Long.TYPE, Predicate.class)); // negative time is considered as zero time addOverride(new ParamOverride(Single.class, 0, ParamMode.ANY, "delaySubscription", Long.TYPE, TimeUnit.class)); @@ -465,6 +481,11 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLast", Long.TYPE, TimeUnit.class)); addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLast", Long.TYPE, TimeUnit.class, Scheduler.class)); + // negative time is considered as zero time + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Scheduler.class)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Boolean.TYPE)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "throttleLatest", Long.TYPE, TimeUnit.class, Scheduler.class, Boolean.TYPE)); // negative buffer time is considered as zero buffer time addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "window", Long.TYPE, TimeUnit.class)); @@ -557,6 +578,46 @@ public void checkParallelFlowable() { defaultValues.put(ParallelFailureHandling.class, ParallelFailureHandling.ERROR); + @SuppressWarnings("rawtypes") + class MixedConverters implements FlowableConverter, ObservableConverter, SingleConverter, + MaybeConverter, CompletableConverter, ParallelFlowableConverter { + + @Override + public Object apply(ParallelFlowable upstream) { + return upstream; + } + + @Override + public Object apply(Completable upstream) { + return upstream; + } + + @Override + public Object apply(Maybe upstream) { + return upstream; + } + + @Override + public Object apply(Single upstream) { + return upstream; + } + + @Override + public Object apply(Observable upstream) { + return upstream; + } + + @Override + public Object apply(Flowable upstream) { + return upstream; + } + } + + MixedConverters mc = new MixedConverters(); + for (Class<?> c : MixedConverters.class.getInterfaces()) { + defaultValues.put(c, mc); + } + // ----------------------------------------------------------------------------------- defaultInstances = new HashMap<Class<?>, List<Object>>(); @@ -1085,7 +1146,7 @@ public String toString() { static final class NeverObservable extends Observable<Object> { @Override - public void subscribeActual(Observer<? super Object> s) { + public void subscribeActual(Observer<? super Object> observer) { // not invoked, the class is a placeholder default value } @@ -1098,7 +1159,7 @@ public String toString() { static final class NeverSingle extends Single<Object> { @Override - public void subscribeActual(SingleObserver<? super Object> s) { + public void subscribeActual(SingleObserver<? super Object> observer) { // not invoked, the class is a placeholder default value } @@ -1111,7 +1172,7 @@ public String toString() { static final class NeverMaybe extends Maybe<Object> { @Override - public void subscribeActual(MaybeObserver<? super Object> s) { + public void subscribeActual(MaybeObserver<? super Object> observer) { // not invoked, the class is a placeholder default value } @@ -1123,7 +1184,7 @@ public String toString() { static final class NeverCompletable extends Completable { @Override - public void subscribeActual(CompletableObserver s) { + public void subscribeActual(CompletableObserver observer) { // not invoked, the class is a placeholder default value } diff --git a/src/test/java/io/reactivex/PublicFinalMethods.java b/src/test/java/io/reactivex/validators/PublicFinalMethods.java similarity index 96% rename from src/test/java/io/reactivex/PublicFinalMethods.java rename to src/test/java/io/reactivex/validators/PublicFinalMethods.java index b71ce574e5..d3d1c47188 100644 --- a/src/test/java/io/reactivex/PublicFinalMethods.java +++ b/src/test/java/io/reactivex/validators/PublicFinalMethods.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import static org.junit.Assert.fail; @@ -19,6 +19,8 @@ import org.junit.Test; +import io.reactivex.*; + /** * Verifies that instance methods of the base reactive classes are all declared final. */ diff --git a/src/test/java/io/reactivex/TextualAorAn.java b/src/test/java/io/reactivex/validators/TextualAorAn.java similarity index 99% rename from src/test/java/io/reactivex/TextualAorAn.java rename to src/test/java/io/reactivex/validators/TextualAorAn.java index 81aba8ccce..b31f40cfa8 100644 --- a/src/test/java/io/reactivex/TextualAorAn.java +++ b/src/test/java/io/reactivex/validators/TextualAorAn.java @@ -11,7 +11,7 @@ * the License for the specific language governing permissions and limitations under the License. */ -package io.reactivex; +package io.reactivex.validators; import java.io.*; import java.util.*; diff --git a/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java b/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java new file mode 100644 index 0000000000..2da65b36c8 --- /dev/null +++ b/src/test/java/io/reactivex/validators/TooManyEmptyNewLines.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package io.reactivex.validators; + +import java.io.*; +import java.util.*; + +import org.junit.Test; + +/** + * Test verifying there are no 2..5 empty newlines in the code. + */ +public class TooManyEmptyNewLines { + + @Test + public void tooManyEmptyNewLines2() throws Exception { + findPattern(2); + } + + @Test + public void tooManyEmptyNewLines3() throws Exception { + findPattern(3); + } + + @Test + public void tooManyEmptyNewLines4() throws Exception { + findPattern(4); + } + + @Test + public void tooManyEmptyNewLines5() throws Exception { + findPattern(5); + } + + static void findPattern(int newLines) throws Exception { + File f = MaybeNo2Dot0Since.findSource("Flowable"); + if (f == null) { + System.out.println("Unable to find sources of TestHelper.findSourceDir()"); + return; + } + + Queue<File> dirs = new ArrayDeque<File>(); + + StringBuilder fail = new StringBuilder(); + fail.append("The following code pattern was found: "); + fail.append("\\R"); + for (int i = 0; i < newLines; i++) { + fail.append("\\R"); + } + fail.append("\n"); + + File parent = f.getParentFile(); + + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/'))); + dirs.offer(new File(parent.getAbsolutePath().replace('\\', '/').replace("src/main/java", "src/test/java"))); + + int total = 0; + + while (!dirs.isEmpty()) { + f = dirs.poll(); + + File[] list = f.listFiles(); + if (list != null && list.length != 0) { + + for (File u : list) { + if (u.isDirectory()) { + dirs.offer(u); + } else { + String fname = u.getName(); + if (fname.endsWith(".java")) { + + List<String> lines = new ArrayList<String>(); + BufferedReader in = new BufferedReader(new FileReader(u)); + try { + for (;;) { + String line = in.readLine(); + if (line == null) { + break; + } + lines.add(line); + } + } finally { + in.close(); + } + + for (int i = 0; i < lines.size() - newLines; i++) { + String line1 = lines.get(i); + if (line1.isEmpty()) { + int c = 1; + for (int j = i + 1; j < lines.size(); j++) { + if (lines.get(j).isEmpty()) { + c++; + } else { + break; + } + } + + if (c == newLines) { + fail + .append(fname) + .append("#L").append(i + 1) + .append("\n"); + total++; + i += c; + } + } + } + } + } + } + } + } + if (total != 0) { + fail.append("Found ") + .append(total) + .append(" instances"); + System.out.println(fail); + throw new AssertionError(fail.toString()); + } + } +}